JavaScript must be enabled to play.
Browser lacks capabilities required to play.
Upgrade or switch to another browser.
Loading…
<<if not $audioInitialized>> <<script>> AudioManager.init(); <</script>> <<set $audioInitialized = true>> <</if>> <<script>> AudioManager.playMusic('main_soundtrack'); <</script>> <div class="fullscreenbackground"> <div class="mainmenu"> <div class="menu_scroll"></div> <div class="menu-container"> <div class="menu-buttons"> <button class="mm-new-game" onclick="goToPassage('Disclaimer')"><span>Start</span></button> <button class="mm-load-game" onclick="window.saveGame()"><span>Load</span></button> <button class="mm-settings" onclick="openSettings()"><span>Settings</span></button> </div> </div> <div class="support-banners"> <div class="support-links"> <a href="https://subscribestar.adult/bemorian-studios" target="_blank" class="support-link subscribestar"> <img src="Bin/Contents/UI/Support/SubscribeStar_Logo.png" alt="SubscribeStar"> </a> <a href="https://www.patreon.com/c/Bemorian_Studios" target="_blank" class="support-link patreon"> <img src="Bin/Contents/UI/Support/Patreon_Logo.png" alt="Patreon"> </a> <a href="https://bemorian-studios.itch.io/wob" target="_blank" class="support-link itchio"> <img src="Bin/Contents/UI/Support/Itchio_Logo.png" alt="itch.io"> </a> </div> <div class="support-disclaimer"> <span class="highlight-red">Tier benefits</span> includes <span class="highlight-yellow">unique quest lines</span>, a <span class="highlight-yellow">customizable companion</span>, and <span class="highlight-yellow">fully voiced NPCs</span> release of the game </div> </div> <div class="version-banner"> <div class="version-icon" onclick="handleShieldClick()"> <img src="Bin/Contents/UI/Support/WOB_Logo.png" alt="Version"> </div> <div class="version-text"> <p>Version: 0.1A</p> <p>(Public Build)</p> </div> </div> </div> </div> <script> window.clickCount = window.clickCount || 0; window.shield_icon_animations = window.shield_icon_animations || ['shield-bash', 'golden-flash', 'shield-spin', 'power-pulse', 'ancient-rune']; function handleShieldClick() { const img = document.querySelector('.version-icon img'); window.shield_icon_animations.forEach(anim => img.classList.remove(anim)); void img.offsetWidth; img.classList.add(window.shield_icon_animations[window.clickCount % window.shield_icon_animations.length]); window.clickCount++; } // Make sure DOM is loaded before attaching events document.addEventListener('DOMContentLoaded', function() { const versionIcon = document.querySelector('.version-icon'); if (versionIcon) { versionIcon.addEventListener('click', handleShieldClick); } }); </script>
<<script>> AudioManager.playVoice('disclaimer'); <</script>> <div class="fullscreenbackground"> <center> <div class="forestbackground"> <!-- Disclaimer overlay --> <div class="disclaimer"></div> <!-- Agreed button --> <div class="agree_button_3" onclick="goToPassage('Intro1')"> <p>Proceed</p> <<script>> AudioManager.playSFX(''); <</script>> </div> </div> </center> </div>
<<script>> AudioManager.stopVoice(); setTimeout(function() { AudioManager.playVoice('intro1'); }, 1000); $(document).on(':passagedisplay', function() { $('.story-text').css('opacity', 0).animate({ opacity: 1 }, 2000); $('.story-column').each(function(index) { $(this).delay(1000 * index).animate({ opacity: 1 }, 1500); }); setTimeout(function() { initLoreTooltips(); }, 500); }); function initLoreTooltips() { if ($('#lore-tooltip').length === 0) { $('body').append('<div id="lore-tooltip"></div>'); } $('.lore-keyword').off('mouseenter mouseleave'); $('.lore-keyword').hover( function(e) { const lore = $(this).data('lore'); const title = $(this).text(); $('#lore-tooltip') .html('<h3>' + title + '</h3><p>' + lore + '</p>') .css({ left: e.pageX + 15, top: e.pageY - 80 }) .addClass('active'); }, function() { $('#lore-tooltip').removeClass('active'); } ); } <</script>> <div class="fullscreenbackground"> <center> <div class="moon_background"> <div class="starry-background"></div> <div class="story-text"> <div class="story-column left-column"> <p>Two centuries ago, the world of <span class="lore-keyword" data-lore="Bemoria is a world torn between memory and ruin. Once a thriving realm where Humans, Elves, Dwarves, and Orcs shared uneasy prosperity, it has become a scarred world forged in centuries of war.">Bemoria</span> stood at the height of its golden age. An era where trade flowed freely between all races. But ambition, fear, and ancient grudges festered beneath the surface. And now that era is little more than a distant memory, shattered by the onset of an unrelenting war.</p> <p><span class="lore-keyword" data-lore="Αn unending conflict that has scarred the land for over two centuries. It began with disputes over borders, trade, and resources, but quickly consumed all who dwelled upon the continent. What should have been brief skirmishes ignited into a conflagration of blood and steel, passed from fathers to sons, from one generation to the next, until war itself became the only constant.">The Great War</span>, as it came to be known, began with disputes over lands and resources. But what started as border skirmishes soon ignited into a generations-long conflict. For 200 years, the races have waged war across fields, forests, and ruins each driven by pride, vengeance, and an unyielding hunger for power.</p> <p>As always, <span class="lore-keyword" data-lore="Born with restless ambition, Humans thrive in every land they claim. They value adaptability, cunning, and the pursuit of power. To a Human, honor is not fixed but shaped by circumstance, measured in victories won, crowns seized, and legacies secured.">Humans</span> are their own worst enemies. warring not just with others, but among themselves. Even in the shadow of greater threats, they vie for power, clawing at crowns and kindreds alike. Kingdoms fracture, alliances crumble, and ambition fuels a cycle they cannot escape.</p> </div> <div class="story-column right-column"> <p><span class="lore-keyword" data-lore="Born in the depths of stone and raised by the forge, Dwarves value resilience, craftsmanship, and loyalty. To a Dwarf, honor is proven through endurance, by work that lasts, oaths unbroken, and kin defended until the last breath.">The Dwarves</span>, ever pragmatic, flourished as the world burned. Their forges roared day and night, crafting weapons for any who could pay. As they say: "friend or foe, it mattered not. Rich in gold but scorned by all", the dwarves became merchants of war.</p> <p><span class="lore-keyword" data-lore="Born beneath ancient canopies and nurtured by timeless traditions, Elves value wisdom, harmony, and remembrance. To an Elf, honor is found in preserving what endures, whether a sacred grove, a bloodline, or the memory of ages past.">The Elves</span> however were drawn into the war to defend their sacred groves, fought valiantly but were overwhelmed as they now resides in their last stronghold.</p> <p><span class="lore-keyword" data-lore="Born into harsh lands where only the strong endure, they value strength, and courage above all else. To an Orc, honor is proven not by words but by deeds">The Orcs</span>, long scorned as savages, emerged as a rising storm. united under mighty warlords and struck with brutal force, carving their place in the world through bloodshed and conquest.</p> <p>Now, the World of Bemoria stands on the brink. a shattered land scarred by centuries of bloodshed and betrayal. Kingdoms lie in ruins. Ancient alliances are forgotten. plunging Bemoria into an age darker than any before.</p> </div> </div> <div class="agree_button_2" onclick="goToPassage('Intro2')"> <<script>> AudioManager.playSFX(''); <</script>> </div> </div> </center> </div>
<<script>> AudioManager.stopVoice(); setTimeout(function() { AudioManager.playVoice('intro2'); }, 1200); $(document).on(':passagedisplay', function() { $('.castle-content').css('opacity', 0).animate({ opacity: 1 }, 1500); $('.castle-paragraph-1').css('opacity', 0).delay(300).animate({ opacity: 1 }, 1200); $('.castle-paragraph-2').css('opacity', 0).delay(1800).animate({ opacity: 1 }, 1200); animateContinueButton(); }); function animateContinueButton() { $('.agree_button_3').css('opacity', 0); setTimeout(function() { $('.agree_button_3').animate({ opacity: 1 }, 800); }, 3000); } <</script>> <div class="fullscreenbackground"> <center> <div class="castle_background"> <div class="castle-content"> <div class="castle-paragraph-1">Fate has placed you among the privileged few— Heir to an Independent lord in these lands, bound by blood and title to the land you will inherit. Your birthright grants you dominion over your fief, the authority to raise armies, and the freedom to forge alliances or crush your enemies. Yet, this is a world where privilege comes with peril, and the choices you make will echo across the land.</div> <div class="castle-paragraph-2">But before you take your first steps into the turbulent world of Bemoria, we must uncover who you truly are.</div> </div> <div class="agree_button_3" onclick="goToPassage('CharacterCreation')"> <p>Proceed</p> <<script>> AudioManager.playSFX(''); <</script>> </div> </div> </center> </div>
<<include "LootCatalog">> <<run setup.itemCatalog = clone($itemCatalog)>> <<set $assets = { audio: { main_soundtrack: "Bin/Contents/UI/Sounds/Musics/Main_Menu_Music.wav", disclaimer: "Bin/Contents/UI/Sounds/SFX/Disclaimer.wav", intro1: "Bin/Contents/Scenes/Human/Intro/Audio/Book_full.wav", intro2: "Bin/Contents/Scenes/Human/Intro/Audio/intro2.wav", intro3: "Bin/Contents/Scenes/Human/Intro/Audio/p1.wav", intro4: "Bin/Contents/Scenes/Human/Intro/Audio/p2.wav", button_Uattributes: "Bin/Contents/UI/Sounds/SFX/Attributes_Increase.wav", button_Dattributes: "Bin/Contents/UI/Sounds/SFX/Attributes_Decrease.wav", button_click_random: "Bin/Contents/UI/Sounds/SFX/Random_Click.wav", button_menu_click: "Bin/Contents/UI/Sounds/SFX/Menu_Click.wav", button_agree_click: "Bin/Contents/UI/Sounds/SFX/Agree_Click.wav", button_click_2: "Bin/Contents/UI/Sounds/SFX/Click_Neutral.wav", button_click: "Bin/Contents/UI/Sounds/SFX/Click_Neutral.wav", button_hover: "Bin/Contents/UI/Sounds/SFX/Hover_Button.wav", button_hover_2: "Bin/Contents/UI/Sounds/SFX/Roll_Successful.wav", map_ui_click: "Bin/Contents/UI/Sounds/SFX/Click_Neutral.wav", map_ui_hover: "Bin/Contents/UI/Sounds/SFX/Hover_Button.wav", map_node_click: "Bin/Contents/UI/Sounds/SFX/Click_Neutral.wav", map_node_hover: "Bin/Contents/UI/Sounds/SFX/Hover_Button.wav", map_claim_click: "Bin/Contents/UI/Sounds/SFX/Agree_Click.wav", map_enter_click: "Bin/Contents/UI/Sounds/SFX/Menu_Click.wav", button_delete: "Bin/Contents/UI/Sounds/SFX/Save_Delete.wav", button_click_Support: "Bin/Contents/UI/Sounds/SFX/Support_Link_Click.wav", button_click_Shield: "Bin/Contents/UI/Sounds/SFX/Click_Shield_1.wav", button_click_Shield_Final: "Bin/Contents/UI/Sounds/SFX/Click_Shield_2.wav", button_click_door_open: "Bin/Contents/UI/Sounds/SFX/Door Opening.wav", button_door_knocking: "Bin/Contents/UI/Sounds/SFX/Door Knocking.wav", button_hover_basic: "Bin/Contents/UI/Sounds/SFX/Hover.wav", button_click_building_placed: "Bin/Contents/UI/Sounds/SFX/Building_Placed.wav", button_page_flip_1: "Bin/Contents/UI/Sounds/SFX/Page_Flip_1.wav", button_page_flip_2: "Bin/Contents/UI/Sounds/SFX/Page_Flip_2.wav", button_page_flip_3: "Bin/Contents/UI/Sounds/SFX/Page_Flip_3.wav", quest_alert: "Bin/Contents/UI/Sounds/SFX/Quest_Alert.wav", quest_abandoned: "Bin/Contents/UI/Sounds/SFX/Quest_Abandoned.wav", roll_failed: "Bin/Contents/UI/Sounds/SFX/Roll_Failed.wav", battle_distant: "Bin/Contents/UI/Sounds/SFX/Battle_Distant.wav", battle_distant_alt: "Bin/Contents/UI/Sounds/SFX/Battle (Distant).wav", combat_music_arvanian_1: "Bin/Contents/UI/Sounds/Musics/Combat_Arvanian_1.wav", disclaimer_mp3: "Bin/Contents/UI/Sounds/SFX/disclaimer.mp3", undressing: "Bin/Contents/UI/Sounds/SFX/Undressing.wav", item_equip: "Bin/Contents/UI/Sounds/SFX/Item_equip.wav", item_unequip: "Bin/Contents/UI/Sounds/SFX/Item_unequip.wav", party_follow: "Bin/Contents/UI/Sounds/SFX/Party_Follow.wav", party_unfollow: "Bin/Contents/UI/Sounds/SFX/Party_Unfollow.wav", ambience_bedroom: "Bin/Contents/UI/Sounds/Ambience/Bedroom_Ambience.wav", ambience_castle_entrance: "Bin/Contents/UI/Sounds/Ambience/Castle_Entrence_Ambience.wav", ambience_cellar: "Bin/Contents/UI/Sounds/Ambience/Cellar_Ambience.wav", ambience_character_creation: "Bin/Contents/UI/Sounds/Ambience/Character_Creation_Ambience.wav", ambience_night_forest: "Bin/Contents/UI/Sounds/Ambience/Night_Forest_Ambience.wav", ambience_tavern: "Bin/Contents/UI/Sounds/Ambience/Tavern_Ambience.wav", ambient_castle: "Bin/Contents/UI/Sounds/Ambience/Castle_Entrence_Ambience.wav", ambient_town: "Bin/Contents/UI/Sounds/Ambience/Character_Creation_Ambience.wav", ambient_forest: "Bin/Contents/UI/Sounds/Ambience/Night_Forest_Ambience.wav", ambient_tavern: "Bin/Contents/UI/Sounds/Ambience/Tavern_Ambience.wav", ambient_forge: "Bin/Contents/UI/Sounds/Ambience/Cellar_Ambience.wav", ambient_market: "Bin/Contents/UI/Sounds/Ambience/Character_Creation_Ambience.wav", combat_battle_start: "Bin/Contents/Combat/Sound/Battle_Start.wav", combat_bow_hit: "Bin/Contents/Combat/Sound/Bow_Hit.wav", combat_bow_miss: "Bin/Contents/Combat/Sound/Bow_Miss.wav", combat_bow_shield_block: "Bin/Contents/Combat/Sound/Bow_Shield_Block.wav", combat_bow_flame_hit: "Bin/Contents/Combat/Sound/Bow_Flame_Hit.wav", combat_bow_flame_miss: "Bin/Contents/Combat/Sound/Bow_Flame_Miss.wav", combat_bow_flame_shield_block: "Bin/Contents/Combat/Sound/Bow_Flame_Shield_Block.wav", combat_crossbow_hit: "Bin/Contents/Combat/Sound/Crossbow_Hit.wav", combat_crossbow_miss: "Bin/Contents/Combat/Sound/Crossbow_Miss.wav", combat_crossbow_shield_block: "Bin/Contents/Combat/Sound/Crossbow_Shield_Block.wav", combat_fist_hit: "Bin/Contents/Combat/Sound/Fist_Hit.wav", combat_fist_miss: "Bin/Contents/Combat/Sound/Fist_Miss.wav", combat_fist_shield_block: "Bin/Contents/Combat/Sound/Fist_Shield_Block.wav", combat_kick_hit: "Bin/Contents/Combat/Sound/Kick_Hit.wav", combat_kick_miss: "Bin/Contents/Combat/Sound/Kick_Miss.wav", combat_strike_hit: "Bin/Contents/Combat/Sound/Strike_Hit.wav", combat_strike_miss: "Bin/Contents/Combat/Sound/Strike_Miss.wav", combat_strike_shield_block: "Bin/Contents/Combat/Sound/Strike_Shield_Block.wav", combat_throwing_knife_hit: "Bin/Contents/Combat/Sound/Throwing_Knife_Hit.wav", combat_throwing_knife_miss: "Bin/Contents/Combat/Sound/Throwing_Knife_Miss.wav", combat_throwing_knife_shield_block: "Bin/Contents/Combat/Sound/Throwing_Knife_Shield_Block.wav", hit_impact: "Bin/Contents/Combat/Sound/Strike_Hit.wav", critical_impact: "Bin/Contents/Combat/Sound/Strike_Hit.wav", ultimate_impact: "Bin/Contents/Combat/Sound/Strike_Hit.wav", heal: "Bin/Contents/Combat/Sound/Skill_Inspire_Mixed.wav", buff: "Bin/Contents/Combat/Sound/Skill_Inspire_Mixed.wav", combat_skill_inspire_female: "Bin/Contents/Combat/Sound/Skill_Inspire_Female.wav", combat_skill_inspire_male: "Bin/Contents/Combat/Sound/Skill_Inspire_Male.wav", combat_skill_inspire_mixed: "Bin/Contents/Combat/Sound/Skill_Inspire_Mixed.wav", combat_end_turn: "Bin/Contents/UI/Sounds/SFX/Combat_End_Turn.wav", combat_player_turn: "Bin/Contents/UI/Sounds/SFX/Combat_Player_Turn.wav", combat_voice_enemy_female_attack: "Bin/Contents/Combat/Sound/Voice/Enemy/Female/Attack.wav", combat_voice_enemy_female_damaged: "Bin/Contents/Combat/Sound/Voice/Enemy/Female/Damaged.wav", combat_voice_enemy_female_death: "Bin/Contents/Combat/Sound/Voice/Enemy/Female/Death.wav", combat_voice_enemy_female_kick: "Bin/Contents/Combat/Sound/Voice/Enemy/Female/Kick.wav", combat_voice_enemy_male_attack: "Bin/Contents/Combat/Sound/Voice/Enemy/Male/Attack.wav", combat_voice_enemy_male_damaged: "Bin/Contents/Combat/Sound/Voice/Enemy/Male/Damaged.wav", combat_voice_enemy_male_death: "Bin/Contents/Combat/Sound/Voice/Enemy/Male/Death.wav", combat_voice_enemy_male_kick: "Bin/Contents/Combat/Sound/Voice/Enemy/Male/Kick.wav", combat_voice_player_human_blunt_damage: "Bin/Contents/Combat/Sound/Voice/Player_Human/Blunt_Damage.wav", combat_voice_player_human_death: "Bin/Contents/Combat/Sound/Voice/Player_Human/Death.wav", combat_voice_player_human_kick: "Bin/Contents/Combat/Sound/Voice/Player_Human/Kick.wav", combat_voice_player_human_sharp_damage: "Bin/Contents/Combat/Sound/Voice/Player_Human/Sharp_Damage.wav", combat_voice_player_human_strike: "Bin/Contents/Combat/Sound/Voice/Player_Human/Strike.wav", combat_voice_sp1_blunt_damage: "Bin/Contents/Combat/Sound/Voice/SP1/Blunt_Damage.wav", combat_voice_sp1_death: "Bin/Contents/Combat/Sound/Voice/SP1/Death.wav", combat_voice_sp1_kick: "Bin/Contents/Combat/Sound/Voice/SP1/Kick.wav", combat_voice_sp1_sharp_damage: "Bin/Contents/Combat/Sound/Voice/SP1/Sharp_Damage.wav", combat_voice_sp1_strike: "Bin/Contents/Combat/Sound/Voice/SP1/Strike.wav", combat_voice_sp3_blunt_damage: "Bin/Contents/Combat/Sound/Voice/SP3/Blunt_Damage.wav", combat_voice_sp3_death: "Bin/Contents/Combat/Sound/Voice/SP3/Death.wav", combat_voice_sp3_kick: "Bin/Contents/Combat/Sound/Voice/SP3/Kick.wav", combat_voice_sp3_sharp_damage: "Bin/Contents/Combat/Sound/Voice/SP3/Sharp_Damage.wav", combat_voice_sp3_strike: "Bin/Contents/Combat/Sound/Voice/SP3/Strike.wav", combat_voice_sp4_blunt_damage: "Bin/Contents/Combat/Sound/Voice/SP4/Blunt_Damage.wav", combat_voice_sp4_death: "Bin/Contents/Combat/Sound/Voice/SP4/Death.wav", combat_voice_sp4_kick: "Bin/Contents/Combat/Sound/Voice/SP4/Kick.wav", combat_voice_sp4_sharp_damage: "Bin/Contents/Combat/Sound/Voice/SP4/Sharp_Damage.wav", combat_voice_sp4_strike: "Bin/Contents/Combat/Sound/Voice/SP4/Strike.wav", combat_voice_sp5_blunt_damage: "Bin/Contents/Combat/Sound/Voice/SP5/Blunt_Damage.wav", combat_voice_sp5_death: "Bin/Contents/Combat/Sound/Voice/SP5/Death.wav", combat_voice_sp5_kick: "Bin/Contents/Combat/Sound/Voice/SP5/Kick.wav", combat_voice_sp5_sharp_damage: "Bin/Contents/Combat/Sound/Voice/SP5/Sharp_Damage.wav", combat_voice_sp5_strike: "Bin/Contents/Combat/Sound/Voice/SP5/Strike.wav", combat_voice_sp6_blunt_damage: "Bin/Contents/Combat/Sound/Voice/SP6/Blunt_Damage.wav", combat_voice_sp6_death: "Bin/Contents/Combat/Sound/Voice/SP6/Death.wav", combat_voice_sp6_kick: "Bin/Contents/Combat/Sound/Voice/SP6/Kick.wav", combat_voice_sp6_sharp_damage: "Bin/Contents/Combat/Sound/Voice/SP6/Sharp_Damage.wav", combat_voice_sp6_strike: "Bin/Contents/Combat/Sound/Voice/SP6/Strike.wav", combat_voice_sp7_blunt_damage: "Bin/Contents/Combat/Sound/Voice/SP7/Blunt_Damage.wav", combat_voice_sp7_death: "Bin/Contents/Combat/Sound/Voice/SP7/Death.wav", combat_voice_sp7_kick: "Bin/Contents/Combat/Sound/Voice/SP7/Kick.wav", combat_voice_sp7_sharp_damage: "Bin/Contents/Combat/Sound/Voice/SP7/Sharp_Damage.wav", combat_voice_sp7_strike: "Bin/Contents/Combat/Sound/Voice/SP7/Strike.wav", combat_voice_sp8_blunt_damage: "Bin/Contents/Combat/Sound/Voice/SP8/Blunt_Damage.wav", combat_voice_sp8_death: "Bin/Contents/Combat/Sound/Voice/SP8/Death.wav", combat_voice_sp8_kick: "Bin/Contents/Combat/Sound/Voice/SP8/Kick.wav", combat_voice_sp8_sharp_damage: "Bin/Contents/Combat/Sound/Voice/SP8/Sharp_Damage.wav", combat_voice_sp8_strike: "Bin/Contents/Combat/Sound/Voice/SP8/Strike.wav", combat_voice_sp9_blunt_damage: "Bin/Contents/Combat/Sound/Voice/SP9/Blunt_Damage.wav", combat_voice_sp9_death: "Bin/Contents/Combat/Sound/Voice/SP9/Death.wav", combat_voice_sp9_kick: "Bin/Contents/Combat/Sound/Voice/SP9/Kick.wav", combat_voice_sp9_sharp_damage: "Bin/Contents/Combat/Sound/Voice/SP9/Sharp_Damage.wav", combat_voice_sp9_strike: "Bin/Contents/Combat/Sound/Voice/SP9/Strike.wav", combat_voice_sp10_blunt_damage: "Bin/Contents/Combat/Sound/Voice/SP10/Blunt_Damage.wav", combat_voice_sp10_death: "Bin/Contents/Combat/Sound/Voice/SP10/Death.wav", combat_voice_sp10_kick: "Bin/Contents/Combat/Sound/Voice/SP10/Kick.wav", combat_voice_sp10_sharp_damage: "Bin/Contents/Combat/Sound/Voice/SP10/Sharp_Damage.wav", combat_voice_sp10_strike: "Bin/Contents/Combat/Sound/Voice/SP10/Strike.wav", guard_gs_000_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-RD-GS000-01.wav", guard_gs_000_02: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-RD-GS000-02.wav", guard_gs_000_03: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-RD-GS000-03.wav", guard_gs_000_04: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-RD-GS000-04.wav", narrator_pss000: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS000-01.wav", narrator_pss100_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS100-01.wav", narrator_pss100_02: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS100-02.wav", narrator_pss101_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS101-01.wav", narrator_pss101_02: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS101-02.wav", narrator_pss102_03: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS101-03.wav", narrator_pss103_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS103-01.wav", narrator_pss103_02: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS103-02.wav", narrator_pss104_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS104-01.wav", narrator_pss104_02: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS104-02.wav", narrator_pss105_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS105-01.wav", narrator_pss106_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS106-01.wav", narrator_pss106_02: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS106-02.wav", narrator_pss200_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS200-01.wav", narrator_pss201_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS201-01.wav", narrator_pss202a_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS202A-01.wav", narrator_pss202a_02: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS202A-02.wav", narrator_pss202b_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS202B-01.wav", narrator_pss202b_02: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS202B-02.wav", narrator_pss203_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS203A-01.wav", narrator_pss203_02: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS203A-02.wav", narrator_pss204_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS204-01.wav", narrator_pss205_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS205-01.wav", narrator_pss300_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-NA-PSS300-01.wav", bella_pss100_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-B-PSS100-01.wav", bella_pss100_02: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-B-PSS100-02.wav", bella_pss101_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-B-PSS101-01.wav", bella_pss103_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-B-PSS103-01.wav", bella_pss104_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-B-PSS104-01.wav", bella_pss105_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-B-PSS105-01.wav", bella_pss106_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-B-PSS106-01.wav", bella_pss200_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-B-PSS200-01.wav", bella_pss200_02: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-B-PSS200-02.wav", bella_pss201_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-B-PSS201-01.wav", bella_pss202a_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-B-PSS202A-01.wav", bella_pss202b_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-B-PSS202B-01.wav", bella_pss203a_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-B-PSS203A-01.wav", bella_pss204_01: "Bin/Contents/Scenes/Human/Prologue/Audio/VO-B-PSS204-01.wav", Inter00_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R1/Inter00_R1_01.wav", Inter01_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R1/Inter01_R1_01.wav", Inter02_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R1/Inter02_R1_01.wav", Inter03_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R1/Inter03_R1_01.wav", Inter04_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R1/Inter04_R1_01.wav", Inter10_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R1/Inter10_R1_01.wav", Inter13_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R1/Inter13_R1_01.wav", Inter14_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R1/Inter14_R1_01.wav", Inter15_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R1/Inter15_R1_01.wav", Inter00_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R2/Inter00_R2_01.wav", Inter01_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R2/Inter01_R2_01.wav", Inter02_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R2/Inter02_R2_01.wav", Inter03_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R2/Inter03_R2_01.wav", Inter04_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R2/Inter04_R2_01.wav", Inter06_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R2/Inter06_R2_01.wav", Inter10_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R2/Inter10_R2_01.wav", Inter13_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R2/Inter13_R2_01.wav", Inter14_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R2/Inter14_R2_01.wav", Inter15_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R2/Inter15_R2_01.wav", Inter00_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter00_R3_01.wav", Inter01_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter01_R3_01.wav", Inter02_R3_00: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_00.wav", Inter02_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_01.wav", Inter02_R3_02: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_02.wav", Inter02_R3_03: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_03.wav", Inter02_R3_04: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_04.wav", Inter02_R3_05: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_05.wav", Inter02_R3_06: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_06.wav", Inter02_R3_07: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_07.wav", Inter02_R3_08: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_08.wav", Inter02_R3_09: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_09.wav", Inter02_R3_10: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_10.wav", Inter02_R3_11: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_11.wav", Inter02_R3_12: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_12.wav", Inter02_R3_13: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_13.wav", Inter02_R3_14: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_14.wav", Inter02_R3_15: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_15.wav", Inter02_R3_16: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_16.wav", Inter02_R3_17: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_17.wav", Inter02_R3_18: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_18.wav", Inter02_R3_19: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter02_R3_19.wav", Inter03_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter03_R3_01.wav", Inter04_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter04_R3_01.wav", Inter10_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter10_R3_01.wav", Inter14_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter14_R3_01.wav", Inter15_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R3/Inter15_R3_01.wav", Inter00_R4_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R4/Inter00_R4_01.wav", Inter01_R4_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R4/Inter01_R4_01.wav", Inter03_R4_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R4/Inter03_R4_01.wav", Inter04_R4_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R4/Inter04_R4_01.wav", Inter06_R4_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R4/Inter06_R4_01.wav", Inter07_R4_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R4/Inter07_R4_01.wav", Inter10_R4_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R4/Inter10_R4_01.wav", Inter15_R4_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R4/Inter15_R4_01.wav", Inter00_R5_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R5/Inter00_R5_01.wav", Inter01_R5_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R5/Inter01_R5_01.wav", Inter03_R5_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R5/Inter03_R5_01.wav", Inter04_R5_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R5/Inter04_R5_01.wav", Inter10_R5_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R5/Inter10_R5_01.wav", Inter15_R5_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R5/Inter15_R5_01.wav", Inter00_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R6/Inter00_R6_01.wav", Inter01_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R6/Inter01_R6_01.wav", Inter03_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R6/Inter03_R6_01.wav", Inter04_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R6/Inter04_R6_01.wav", Inter06_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R6/Inter06_R6_01.wav", Inter10_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R6/Inter10_R6_01.wav", Inter14_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R6/Inter14_R6_01.wav", Inter15_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R6/Inter15_R6_01.wav", Inter00_R7_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R7/Inter00_R7_01.wav", Inter01_R7_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R7/Inter01_R7_01.wav", Inter03_R7_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R7/Inter03_R7_01.wav", Inter04_R7_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R7/Inter04_R7_01.wav", Inter10_R7_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R7/Inter10_R7_01.wav", Inter14_R7_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R7/Inter14_R7_01.wav", Inter15_R7_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R7/Inter15_R7_01.wav", Inter00_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R8/Inter00_R8_01.wav", Inter01_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R8/Inter01_R8_01.wav", Inter03_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R8/Inter03_R8_01.wav", Inter04_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R8/Inter04_R8_01.wav", Inter07_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R8/Inter07_R8_01.wav", Inter10_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R8/Inter10_R8_01.wav", Inter14_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R8/Inter14_R8_01.wav", Inter15_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Voices/R8/Inter15_R8_01.wav", SP6_Inter00_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R1/Inter00_R1_01.wav", SP6_Inter01_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R1/Inter01_R1_01.wav", SP6_Inter02_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R1/Inter02_R1_01.wav", SP6_Inter03_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R1/Inter03_R1_01.wav", SP6_Inter04_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R1/Inter04_R1_01.wav", SP6_Inter06_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R1/Inter06_R1_01.wav", SP6_Inter07_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R1/Inter07_R1_01.wav", SP6_Inter10_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R1/Inter10_R1_01.wav", SP6_Inter13_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R1/Inter13_R1_01.wav", SP6_Inter14_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R1/Inter14_R1_01.wav", SP6_Inter15_R1_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R1/Inter15_R1_01.wav", SP6_Inter00_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R2/Inter00_R2_01.wav", SP6_Inter01_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R2/Inter01_R2_01.wav", SP6_Inter02_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R2/Inter02_R2_01.wav", SP6_Inter03_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R2/Inter03_R2_01.wav", SP6_Inter04_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R2/Inter04_R2_01.wav", SP6_Inter06_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R2/Inter06_R2_01.wav", SP6_Inter07_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R2/Inter07_R2_01.wav", SP6_Inter10_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R2/Inter10_R2_01.wav", SP6_Inter13_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R2/Inter13_R2_01.wav", SP6_Inter14_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R2/Inter14_R2_01.wav", SP6_Inter15_R2_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R2/Inter15_R2_01.wav", SP6_Inter00_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter00_R3_01.wav", SP6_Inter01_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter01_R3_01.wav", SP6_Inter02_R3_00: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter02_R3_00.wav", SP6_Inter02_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter02_R3_01.wav", SP6_Inter02_R3_02: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter02_R3_02.wav", SP6_Inter02_R3_03: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter02_R3_03.wav", SP6_Inter02_R3_04: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter02_R3_04.wav", SP6_Inter02_R3_05: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter02_R3_05.wav", SP6_Inter02_R3_06: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter02_R3_06.wav", SP6_Inter02_R3_07: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter02_R3_07.wav", SP6_Inter02_R3_08: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter02_R3_08.wav", SP6_Inter02_R3_09: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter02_R3_09.wav", SP6_Inter02_R3_10: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter02_R3_10.wav", SP6_Inter03_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter03_R3_01.wav", SP6_Inter04_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter04_R3_01.wav", SP6_Inter10_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter10_R3_01.wav", SP6_Inter14_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter14_R3_01.wav", SP6_Inter15_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter15_R3_01.wav", SP6_Inter6_R3_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R3/Inter6_R3_01.wav", SP6_Inter00_R4_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R4/Inter00_R4_01.wav", SP6_Inter01_R4_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R4/Inter01_R4_01.wav", SP6_Inter03_R4_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R4/Inter03_R4_01.wav", SP6_Inter04_R4_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R4/Inter04_R4_01.wav", SP6_Inter10_R4_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R4/Inter10_R4_01.wav", SP6_Inter15_R4_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R4/Inter15_R4_01.wav", SP6_Inter00_R5_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R5/Inter00_R5_01.wav", SP6_Inter01_R5_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R5/Inter01_R5_01.wav", SP6_Inter03_R5_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R5/Inter03_R5_01.wav", SP6_Inter04_R5_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R5/Inter04_R5_01.wav", SP6_Inter10_R5_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R5/Inter10_R5_01.wav", SP6_Inter15_R5_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R5/Inter15_R5_01.wav", SP6_Inter00_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R6/Inter00_R6_01.wav", SP6_Inter01_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R6/Inter01_R6_01.wav", SP6_Inter03_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R6/Inter03_R6_01.wav", SP6_Inter04_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R6/Inter04_R6_01.wav", SP6_Inter10_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R6/Inter10_R6_01.wav", SP6_Inter14_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R6/Inter14_R6_01.wav", SP6_Inter15_R6_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R6/Inter15_R6_01.wav", SP6_Inter00_R7_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R7/Inter00_R7_01.wav", SP6_Inter01_R7_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R7/Inter01_R7_01.wav", SP6_Inter03_R7_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R7/Inter03_R7_01.wav", SP6_Inter04_R7_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R7/Inter04_R7_01.wav", SP6_Inter10_R7_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R7/Inter10_R7_01.wav", SP6_Inter14_R7_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R7/Inter14_R7_01.wav", SP6_Inter15_R7_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R7/Inter15_R7_01.wav", SP6_Inter00_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R8/Inter00_R8_01.wav", SP6_Inter01_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R8/Inter01_R8_01.wav", SP6_Inter03_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R8/Inter03_R8_01.wav", SP6_Inter04_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R8/Inter04_R8_01.wav", SP6_Inter10_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R8/Inter10_R8_01.wav", SP6_Inter14_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R8/Inter14_R8_01.wav", SP6_Inter15_R8_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Voices/R8/Inter15_R8_01.wav", gs_01_001: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-01-001-SP2.wav", gs_01_002: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-01-002-SP2.wav", gs_01_003_na: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-01-003-NA.wav", gs_01_003: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-01-003-SP2.wav", gs_01_004: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-01-004-SP2.wav", gs_01_005: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-01-005-SP2.wav", gs_01_006: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-01-006-SP2.wav", gs_01_007: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-01-007-SP2.wav", gs_01_008: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-01-008-SP2.wav", gs_01_009: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-01-009-SP2.wav", guard_gs_000_01: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-01-001-SP2.wav", guard_gs_000_02: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-01-002-SP2.wav", guard_gs_000_03: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-01-003-SP2.wav", guard_gs_000_04: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-01-003-SP2.wav", gs_02_001: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-001-SP2.wav", gs_02_002_sp0: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-002-SP0.wav", gs_02_002_sp5: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-002-SP5.wav", gs_02_003: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-003-SP2.wav", gs_02_003a: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-003A-SP0.wav", gs_02_004: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-004-SP0.wav", gs_02_005: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-005-SP0.wav", gs_02_006: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-006-SP0.wav", gs_02_008: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-008-SP0.wav", gs_02_009: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-009-SP0.wav", gs_02_010: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-010-SP6.wav", gs_02_011: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-011-SP6.wav", gs_02_015_sp2: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-015-SP2.wav", gs_02_015_sp5: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-015-SP5.wav", gs_02_017ab: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-017AB-SP5.wav", gs_02_018: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-02-018-SP5.wav", gs_03_001: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-03-001-SP3.wav", gs_03_002: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-03-002-SP3.wav", gs_03_003: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-03-003-SP3.wav", gs_03_004: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-03-004-SP3.wav", gs_03_005: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-03-005-SP3.wav", gs_03_006: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-03-006-SP3.wav", gs_03_007: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-03-007-SP3.wav", gs_04_001: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-04-001-SP1.wav", gs_04_002: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-04-002-SP1.wav", gs_04_003: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-04-003-SP1.wav", gs_04_004: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-04-004-SP1.wav", gs_04_005: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-04-005-SP1.wav", gs_04_006: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-04-006-SP1.wav", gs_04_007a: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-04-007A-SP1.wav", gs_04_007b: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-04-007B-SP1.wav", gs_05_001: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-05-001-SP3.wav", gs_05_002: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-05-002-SP3.wav", gs_05_002b: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-05-002-SP3.wav", gs_05_003: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-05-003-SP3.wav", gs_05_004: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-05-004-SP3.wav", gs_05_005: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-05-005-SP3.wav", gs_05_006: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-05-006-SP3.wav", gs_05_007: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-05-007-SP3.wav", gs_05_008: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-05-008-SP3.wav", gs_05_009: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-05-009-SP3.wav", gs_05_010: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-05-010-SP3.wav", gs_05_011: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-05-011-SP3.wav", gs_05_012: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-05-012-SP3.wav", gs_06_001: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-06-001-SP6.wav", gs_06_002: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-06-002-SP6.wav", gs_06_003: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-06-003-SP6.wav", gs_06_004: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-06-004-SP6.wav", gs_06_005: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-06-005-SP6.wav", gs_06_006a: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-06-006A-SP6.wav", gs_06_006b: "Bin/Contents/Scenes/Human/Start/Audio/VO-GS-06-006B-SP6.wav", Inter15_R3_01_01: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Voices/Inter15_R3_01_01.wav", Inter15_R3_01_02: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Voices/Inter15_R3_01_02.wav", Inter15_R3_01_02B: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Voices/Inter15_R3_01_02B.wav", Inter15_R3_01_03: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Voices/Inter15_R3_01_03.wav", Inter15_R3_01_04: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Voices/Inter15_R3_01_04.wav", Inter15_R3_01_05: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Voices/Inter15_R3_01_05.wav", Inter15_R3_01_06: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Voices/Inter15_R3_01_06.wav", Inter15_R3_01_07: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Voices/Inter15_R3_01_07.wav", Inter15_R3_01_08: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Voices/Inter15_R3_01_08.wav", Inter15_R3_01_03NA: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Voices/Inter15_R3_01_03NA.wav", Inter15_R3_01_05NA: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Voices/Inter15_R3_01_05NA.wav", Inter15_R3_01_07NA: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Voices/Inter15_R3_01_07NA.wav", Inter15_R3_01_08NA: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Voices/Inter15_R3_01_08NA.wav", Inter15_R3_01_07_Combined: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Voices/Inter15_R3_01_07_Combined.wav" } }>> <<set $audioInitialized = false>> <<set $blacksmithRecruited = false>> /* Cache only essential audio for playlists - bulk preloading disabled for faster startup */ <<cacheaudio "main_soundtrack" $assets.audio.main_soundtrack>> /* Create playlist */ <<createplaylist "music">> <<track "main_soundtrack">> <</createplaylist>> <<set $musicVolume = 0>> <<set $dialogueVolume = 0>> /*GODODODODODODODODODOODODODODODOODODODODODOODODODODOODODODODODODODODODODODODODO character saves */ <<set $resources to { "food": 200, "wood": 140, "gold": 500, "stone": 50, "coins": 0, "population": 0 }>> :: StoryInit [start]\r\n<<run setup.playerState.reset()>>\r\n<<run setup.syncPlayerAliases()>> /* COMPANIONS */ // In StoryInit passage <<set $characterDatabase = [ { id: "bella_1", name: "Bella", lastName: "Harrowick", gender: "female", recruited: false, portrait: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Card.png", frame: "Bin/Contents/Characters/Human_Storyline/Story/SP1/Frame.png", animationPath: "Bin/Contents/Combat/Companions/Bella/", voicePath: "Bin/Contents/Combat/Sound/Voice/SP1/", title: "Knight", faith: "Solvarion", defaultTitleArchetype: "Knight", defaultRole: "Guard", selectedSkill: "", cityRole: "Guard", cityRoleEffectiveness: null, buildingAssignment: null, scoutLocation: null, scoutDiscoveries: [], rolePerksUnlocked: { "Guard": [], "Scout": [] }, roleTimeSpent: { "Guard": 0, "Scout": 0 }, stats: { attributes: { Strength: 3, Dexterity: 3, Intelligence: 3, Willpower: 3, Discipline: 2, Kindness: 2, Happiness: 2, Beauty: 2, Charisma: 2, Health: 16, Stamina: 14, Affection: 2 }, skills: { Academics: 2, Administration: 2, Medicine: 1, Science: 2, "Melee Combat": 2, "Ranged Combat": 2, Domestic: 1, Acrobatics: 2, Dancing: 1, Performance: 1, Gardening: 1, Survival: 2 }, states: { Fear: 2, Defiance: 5, Stress: 8, Respect: 15, Corruption: 3, Devotion: 12 }, sexual: { Vaginal: 6, Anal: 3, Oral: 5, Kissing: 12, Lesbian: 4, Arousal: 8 } }, examStats: { "Frame": "Petite", "Pregnant": "No", "Nipples": "Not Pierced", "Navel": "Not Pierced", "Collar": "No", "Nipple Wear": "Soft & Fresh", "Vaginal State": "Flexible", "Pubic Presence": "Hairless", "Fertility": "70", "Pierced Clit": "No", "Loads Taken": "0", "Wear": "Tight, Unaffected", "Swallowed Semen": "0.00 L", "Pierced Tongue": "No", "Pierced Lips": "No", "Oral Loads": "0", "Oral Wear": "Fresh", "Anal State": "Virgin", "Anal Plug": "No", "Back Tattoo": "No", "Anal Loads": "0", "Anal Wear": "Tight, Unaffected" } }, { id: "robert_2", name: "Rodric", lastName: "Kain", gender: "male", recruited: true, portrait: "Bin/Contents/Characters/Human_Storyline/Story/SP2/Card.png", frame: "Bin/Contents/Characters/Human_Storyline/Story/SP2/Frame.png", animationPath: "Bin/Contents/Combat/Companions/Robert/", voicePath: "Bin/Contents/Combat/Sound/Voice/SP2/", title: "Knight", faith: "Solvarion", defaultTitleArchetype: "Knight", defaultRole: "General", cityRole: null, cityRoleEffectiveness: null, buildingAssignment: null, selectedSkill: "", stats: { attributes: { Strength: 9, Dexterity: 3, Intelligence: 3, Willpower: 3, Discipline: 2, Kindness: 2, Happiness: 2, Beauty: 2, Charisma: 2, Health: 16, Stamina: 14, Affection: 8 }, skills: { Academics: 2, Administration: 2, Medicine: 1, Science: 2, "Melee Combat": 2, "Ranged Combat": 2, Domestic: 1, Acrobatics: 2, Dancing: 1, Performance: 1, Gardening: 1, Survival: 2 }, states: { Fear: 2, Defiance: 5, Stress: 8, Respect: 15, Corruption: 3, Devotion: 12 }, sexual: { Vaginal: 6, Anal: 3, Oral: 5, Kissing: 12, Lesbian: 4, Arousal: 8 } }, examStats: { "Frame": "Very Petite", "Pregnant": "No", "Nipples": "Not Pierced", "Navel": "Not Pierced", "Collar": "No", "Nipple Wear": "Soft & Fresh", "Vaginal State": "Flexible", "Pubic Presence": "Hairless", "Fertility": "70", "Pierced Clit": "No", "Loads Taken": "0", "Wear": "Tight, Unaffected", "Swallowed Semen": "0.00 L", "Pierced Tongue": "No", "Pierced Lips": "No", "Oral Loads": "0", "Oral Wear": "Fresh", "Anal State": "Virgin", "Anal Plug": "No", "Back Tattoo": "No", "Anal Loads": "0", "Anal Wear": "Tight, Unaffected" } }, { id: "eleanor_3", name: "Eleanor", lastName: "Eleanor", gender: "female", recruited: false, portrait: "Bin/Contents/Characters/Human_Storyline/Story/SP3/Card.png", frame: "Bin/Contents/Characters/Human_Storyline/Story/SP3/Frame.png", voicePath: "Bin/Contents/Combat/Sound/Voice/SP3/", title: "", faith: "Solvarion", defaultTitleArchetype: "Assassin", defaultRole: "Scout", selectedSkill: "", cityRole: "Scout", cityRoleEffectiveness: null, buildingAssignment: null, scoutLocation: null, scoutDiscoveries: [], rolePerksUnlocked: { "Guard": [], "Scout": [] }, roleTimeSpent: { "Guard": 0, "Scout": 0 }, stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 2, Willpower: 1, Discipline: 1, Kindness: 2, Happiness: 1, Beauty: 2, Charisma: 2, Health: 10, Stamina: 9, Affection: 8 }, skills: { Academics: 1, Administration: 1, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 1 }, states: { Fear: 2, Defiance: 5, Stress: 8, Respect: 15, Corruption: 3, Devotion: 12 }, sexual: { Vaginal: 6, Anal: 3, Oral: 5, Kissing: 12, Lesbian: 4, Arousal: 8 } }, examStats: { "Frame": "Petite", "Pregnant": "No", "Nipples": "Not Pierced", "Navel": "Not Pierced", "Collar": "No", "Nipple Wear": "Soft & Fresh", "Vaginal State": "Flexible", "Pubic Presence": "Hairless", "Fertility": "70", "Pierced Clit": "No", "Loads Taken": "0", "Wear": "Tight, Unaffected", "Swallowed Semen": "0.00 L", "Pierced Tongue": "No", "Pierced Lips": "No", "Oral Loads": "0", "Oral Wear": "Fresh", "Anal State": "Virgin", "Anal Plug": "No", "Back Tattoo": "No", "Anal Loads": "0", "Anal Wear": "Tight, Unaffected" } }, { id: "evelyn_4", name: "Evelyn", lastName: "Evelyn", gender: "female", recruited: false, portrait: "Bin/Contents/Characters/Human_Storyline/Story/SP4/Card.png", frame: "Bin/Contents/Characters/Human_Storyline/Story/SP4/Frame.png", voicePath: "Bin/Contents/Combat/Sound/Voice/SP4/", title: "Healer", faith: "Solvarion", defaultTitleArchetype: "Medic", defaultRole: "", selectedSkill: "", cityRole: null, cityRoleEffectiveness: null, buildingAssignment: null, scoutLocation: null, scoutDiscoveries: [], rolePerksUnlocked: { "Guard": [], "Scout": [] }, roleTimeSpent: { "Guard": 0, "Scout": 0 }, stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 2, Willpower: 1, Discipline: 1, Kindness: 2, Happiness: 1, Beauty: 2, Charisma: 2, Health: 10, Stamina: 9, Affection: 8 }, skills: { Academics: 1, Administration: 1, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 1 }, states: { Fear: 2, Defiance: 5, Stress: 8, Respect: 15, Corruption: 3, Devotion: 12 }, sexual: { Vaginal: 6, Anal: 3, Oral: 5, Kissing: 12, Lesbian: 4, Arousal: 8 } }, examStats: { "Frame": "Petite", "Pregnant": "No", "Nipples": "Not Pierced", "Navel": "Not Pierced", "Collar": "No", "Nipple Wear": "Soft & Fresh", "Vaginal State": "Flexible", "Pubic Presence": "Hairless", "Fertility": "70", "Pierced Clit": "No", "Loads Taken": "0", "Wear": "Tight, Unaffected", "Swallowed Semen": "0.00 L", "Pierced Tongue": "No", "Pierced Lips": "No", "Oral Loads": "0", "Oral Wear": "Fresh", "Anal State": "Virgin", "Anal Plug": "No", "Back Tattoo": "No", "Anal Loads": "0", "Anal Wear": "Tight, Unaffected" }, }, { id: "theron_5", name: "Theron", lastName: "Fenric", gender: "male", recruited: true, portrait: "Bin/Contents/Characters/Human_Storyline/Story/SP5/Card.png", frame: "Bin/Contents/Characters/Human_Storyline/Story/SP5/Frame.png", voicePath: "Bin/Contents/Combat/Sound/Voice/SP5/", title: "Knight", faith: "Solvarion", defaultTitleArchetype: "Knight", defaultRole: "General", selectedSkill: "", cityRole: null, cityRoleEffectiveness: null, buildingAssignment: null, scoutLocation: null, scoutDiscoveries: [], rolePerksUnlocked: { "Guard": [], "Scout": [] }, roleTimeSpent: { "Guard": 0, "Scout": 0 }, stats: { attributes: { Strength: 8, Dexterity: 1, Intelligence: 2, Willpower: 1, Discipline: 1, Kindness: 2, Happiness: 1, Beauty: 2, Charisma: 2, Health: 10, Stamina: 9, Affection: 8 }, skills: { Academics: 1, Administration: 1, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 1 }, states: { Fear: 2, Defiance: 5, Stress: 8, Respect: 15, Corruption: 3, Devotion: 12 }, sexual: { Vaginal: 6, Anal: 3, Oral: 5, Kissing: 12, Lesbian: 4, Arousal: 8 } }, examStats: { "Frame": "Petite", "Pregnant": "No", "Nipples": "Not Pierced", "Navel": "Not Pierced", "Collar": "No", "Nipple Wear": "Soft & Fresh", "Vaginal State": "Flexible", "Pubic Presence": "Hairless", "Fertility": "70", "Pierced Clit": "No", "Loads Taken": "0", "Wear": "Tight, Unaffected", "Swallowed Semen": "0.00 L", "Pierced Tongue": "No", "Pierced Lips": "No", "Oral Loads": "0", "Oral Wear": "Fresh", "Anal State": "Virgin", "Anal Plug": "No", "Back Tattoo": "No", "Anal Loads": "0", "Anal Wear": "Tight, Unaffected" } }, { id: "sabine_6", name: "Sabine", lastName: "Orvenn", gender: "female", recruited: false, married: false, portrait: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Card.png", frame: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Frame.png", voicePath: "Bin/Contents/Combat/Sound/Voice/SP6/", title: "Knight", faith: "Solvarion", defaultTitleArchetype: "Knight", defaultRole: "Treasurer", selectedSkill: "", cityRole: null, cityRoleEffectiveness: null, buildingAssignment: null, scoutLocation: null, scoutDiscoveries: [], rolePerksUnlocked: { "Guard": [], "Scout": [] }, roleTimeSpent: { "Guard": 0, "Scout": 0 }, stats: { attributes: { Strength: 7, Dexterity: 1, Intelligence: 2, Willpower: 1, Discipline: 1, Kindness: 2, Happiness: 1, Beauty: 2, Charisma: 2, Health: 10, Stamina: 9, Affection: 4, }, skills: { Academics: 1, Administration: 1, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 1 }, states: { Fear: 2, Defiance: 5, Stress: 8, Respect: 15, Corruption: 3, Devotion: 12 }, sexual: { Vaginal: 6, Anal: 3, Oral: 5, Kissing: 12, Lesbian: 4, Arousal: 8 } }, examStats: { "Frame": "Petite", "Pregnant": "No", "Nipples": "Not Pierced", "Navel": "Not Pierced", "Collar": "No", "Nipple Wear": "Soft & Fresh", "Vaginal State": "Flexible", "Pubic Presence": "Hairless", "Fertility": "70", "Pierced Clit": "No", "Loads Taken": "0", "Wear": "Tight, Unaffected", "Swallowed Semen": "0.00 L", "Pierced Tongue": "No", "Pierced Lips": "No", "Oral Loads": "0", "Oral Wear": "Fresh", "Anal State": "Virgin", "Anal Plug": "No", "Back Tattoo": "No", "Anal Loads": "0", "Anal Wear": "Tight, Unaffected" } }, { id: "henry_7", name: "Henry", lastName: "Skaldrick", gender: "male", recruited: false, portrait: "Bin/Contents/Characters/Human_Storyline/Story/SP7/Card.png", frame: "Bin/Contents/Characters/Human_Storyline/Story/SP7/Frame.png", voicePath: "Bin/Contents/Combat/Sound/Voice/SP7/", title: "Diplomat", faith: "Solvarion", defaultTitleArchetype: "Commander", defaultRole: "Diplomat", selectedSkill: "", cityRole: null, cityRoleEffectiveness: null, buildingAssignment: null, scoutLocation: null, scoutDiscoveries: [], rolePerksUnlocked: { "Guard": [], "Scout": [] }, roleTimeSpent: { "Guard": 0, "Scout": 0 }, stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 10, Willpower: 1, Discipline: 1, Kindness: 2, Happiness: 1, Beauty: 10, Charisma: 5, Health: 10, Stamina: 9, Affection: 8 }, skills: { Academics: 1, Administration: 1, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 1 }, states: { Fear: 2, Defiance: 5, Stress: 8, Respect: 15, Corruption: 3, Devotion: 12 }, sexual: { Vaginal: 6, Anal: 3, Oral: 5, Kissing: 12, Lesbian: 4, Arousal: 8 } }, examStats: { "Frame": "Petite", "Pregnant": "No", "Nipples": "Not Pierced", "Navel": "Not Pierced", "Collar": "No", "Nipple Wear": "Soft & Fresh", "Vaginal State": "Flexible", "Pubic Presence": "Hairless", "Fertility": "70", "Pierced Clit": "No", "Loads Taken": "0", "Wear": "Tight, Unaffected", "Swallowed Semen": "0.00 L", "Pierced Tongue": "No", "Pierced Lips": "No", "Oral Loads": "0", "Oral Wear": "Fresh", "Anal State": "Virgin", "Anal Plug": "No", "Back Tattoo": "No", "Anal Loads": "0", "Anal Wear": "Tight, Unaffected" } }, { id: "fergus_8", name: "Fergus", lastName: "Tervan", gender: "male", recruited: false, portrait: "Bin/Contents/Characters/Human_Storyline/Story/SP8/Card.png", frame: "Bin/Contents/Characters/Human_Storyline/Story/SP8/Frame.png", voicePath: "Bin/Contents/Combat/Sound/Voice/SP8/", title: "Spy", faith: "Solvarion", defaultTitleArchetype: "Assassin", defaultRole: "Spy", selectedSkill: "", cityRole: null, cityRoleEffectiveness: null, buildingAssignment: null, scoutLocation: null, scoutDiscoveries: [], rolePerksUnlocked: { "Guard": [], "Scout": [] }, roleTimeSpent: { "Guard": 0, "Scout": 0 }, stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 2, Willpower: 1, Discipline: 1, Kindness: 2, Happiness: 1, Beauty: 2, Charisma: 2, Health: 10, Stamina: 9, Affection: 8 }, skills: { Academics: 1, Administration: 1, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 1 }, states: { Fear: 2, Defiance: 5, Stress: 8, Respect: 15, Corruption: 3, Devotion: 12 }, sexual: { Vaginal: 6, Anal: 3, Oral: 5, Kissing: 12, Lesbian: 4, Arousal: 8 } }, examStats: { "Frame": "Petite", "Pregnant": "No", "Nipples": "Not Pierced", "Navel": "Not Pierced", "Collar": "No", "Nipple Wear": "Soft & Fresh", "Vaginal State": "Flexible", "Pubic Presence": "Hairless", "Fertility": "70", "Pierced Clit": "No", "Loads Taken": "0", "Wear": "Tight, Unaffected", "Swallowed Semen": "0.00 L", "Pierced Tongue": "No", "Pierced Lips": "No", "Oral Loads": "0", "Oral Wear": "Fresh", "Anal State": "Virgin", "Anal Plug": "No", "Back Tattoo": "No", "Anal Loads": "0", "Anal Wear": "Tight, Unaffected" } }, { id: "dorian_9", name: "Dorian", lastName: "Valdran", gender: "male", recruited: false, portrait: "Bin/Contents/Characters/Human_Storyline/Story/SP9/Card.png", frame: "Bin/Contents/Characters/Human_Storyline/Story/SP9/Frame.png", voicePath: "Bin/Contents/Combat/Sound/Voice/SP9/", title: "Alchemist", faith: "Solvarion", defaultTitleArchetype: "Skirmisher", defaultRole: "Artisan", selectedSkill: "", cityRole: null, cityRoleEffectiveness: null, buildingAssignment: null, scoutLocation: null, scoutDiscoveries: [], rolePerksUnlocked: { "Guard": [], "Scout": [] }, roleTimeSpent: { "Guard": 0, "Scout": 0 }, stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 2, Willpower: 1, Discipline: 1, Kindness: 2, Happiness: 1, Beauty: 2, Charisma: 2, Health: 10, Stamina: 9, Affection: 8 }, skills: { Academics: 1, Administration: 1, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 1 }, states: { Fear: 2, Defiance: 5, Stress: 8, Respect: 15, Corruption: 3, Devotion: 12 }, sexual: { Vaginal: 6, Anal: 3, Oral: 5, Kissing: 12, Lesbian: 4, Arousal: 8 } }, examStats: { "Frame": "Petite", "Pregnant": "No", "Nipples": "Not Pierced", "Navel": "Not Pierced", "Collar": "No", "Nipple Wear": "Soft & Fresh", "Vaginal State": "Flexible", "Pubic Presence": "Hairless", "Fertility": "70", "Pierced Clit": "No", "Loads Taken": "0", "Wear": "Tight, Unaffected", "Swallowed Semen": "0.00 L", "Pierced Tongue": "No", "Pierced Lips": "No", "Oral Loads": "0", "Oral Wear": "Fresh", "Anal State": "Virgin", "Anal Plug": "No", "Back Tattoo": "No", "Anal Loads": "0", "Anal Wear": "Tight, Unaffected" } }, { id: "william_10", name: "William", lastName: "Cavren", gender: "male", recruited: false, portrait: "Bin/Contents/Characters/Human_Storyline/Story/SP10/Card.png", frame: "Bin/Contents/Characters/Human_Storyline/Story/SP10/Frame.png", voicePath: "Bin/Contents/Combat/Sound/Voice/SP10/", title: "Ranger", faith: "Solvarion", defaultTitleArchetype: "Ranger", defaultRole: "Scout", selectedSkill: "", cityRole: null, cityRoleEffectiveness: null, buildingAssignment: null, scoutLocation: null, scoutDiscoveries: [], rolePerksUnlocked: { "Guard": [], "Scout": [] }, roleTimeSpent: { "Guard": 0, "Scout": 0 }, stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 2, Willpower: 1, Discipline: 1, Kindness: 2, Happiness: 1, Beauty: 2, Charisma: 2, Health: 10, Stamina: 9, Affection: 8 }, skills: { Academics: 1, Administration: 1, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 1 }, states: { Fear: 2, Defiance: 5, Stress: 8, Respect: 65, Corruption: 3, Devotion: 12 }, sexual: { Vaginal: 6, Anal: 3, Oral: 5, Kissing: 12, Lesbian: 4, Arousal: 8 } }, examStats: { "Frame": "Petite", "Pregnant": "No", "Nipples": "Not Pierced", "Navel": "Not Pierced", "Collar": "No", "Nipple Wear": "Soft & Fresh", "Vaginal State": "Flexible", "Pubic Presence": "Hairless", "Fertility": "70", "Pierced Clit": "No", "Loads Taken": "0", "Wear": "Tight, Unaffected", "Swallowed Semen": "0.00 L", "Pierced Tongue": "No", "Pierced Lips": "No", "Oral Loads": "0", "Oral Wear": "Fresh", "Anal State": "Virgin", "Anal Plug": "No", "Back Tattoo": "No", "Anal Loads": "0", "Anal Wear": "Tight, Unaffected" } }, ]>> <<set $inventories = { player: { equipped: { weapon: null, armor: null, helmet: null, slot1: null, slot2: null, slot3: null, slot4: null, slot5: null, slot6: null, slot7: null, slot8: null }, gold: 0, capacity: 80 } }>> <<set $sharedInventory = { itemPool: [], equippedItems: {} }>> <<for _char range $characterDatabase>> <<set $inventories[_char.id] = { equipped: { weapon: null, armor: null, helmet: null, slot1: null, slot2: null, slot3: null, slot4: null } }>> <</for>> /* Selected Traits */ <<set $selectedTraits to []>> /* Backstory */ <<set $familyBackground to "">> <<set $childhoodTrait to "">> <<set $teenActivity to "">> <<set $adultAchievement to "">> /* Game State */ <<set $gameDays to 0>> <<set $gameDate to "">> /* Settlement */ <<set $settlement to { "buildings": [], "workers": { "unassigned": 0, "assigned": {} }, "upgrades": {}, "research": [] }>> <<set $characterStatus = { health: { value: 100, max: 100 }, cleanliness: { value: 100, max: 100 }, hunger: { value: 100, max: 100 }, mood: { value: 100, max: 100 } }>> /* Inventory */ <<set $inventory to { "items": [], "maxSpace": 20, "equippedItems": { "weapon": "", "armor": "", "accessory": "" } }>> /* Army */ <<set $army to { "total": 0, "units": { "infantry": 0, "archers": 0, "cavalry": 0, "siege": 0 }, "morale": 100, "experience": 0 }>> /* Companions & Slaves */ <<set $companions to []>> <<set $slaves to []>> /* Relationships */ <<set $relationships to { "factions": {}, "individuals": {} }>> /* Family */ <<set $wife to "">> <<set $concubines to 0>> <<set $children to 0>> /* Quests */ <<set $quests to { "active": [], "completed": [], "failed": [] }>> <<run setup.questBoardConfig = { maxMinorQueue: null, maxMinorActive: null }>> <<run setup.ensureQuestBoardState = function() { var qb = State.variables.questBoard; var createDefault = function() { return { main: null, minorQueue: [], minorActive: [], minorCompleted: [], lastUpdated: Date.now() }; }; if (!qb || typeof qb !== 'object') { State.variables.questBoard = createDefault(); return State.variables.questBoard; } if (!('main' in qb) || (qb.main && typeof qb.main !== 'object')) { qb.main = null; } if (!Array.isArray(qb.minorQueue)) { qb.minorQueue = []; } if (!Array.isArray(qb.minorActive)) { qb.minorActive = []; } if (!Array.isArray(qb.minorCompleted)) { qb.minorCompleted = []; } if (typeof qb.lastUpdated !== 'number') { qb.lastUpdated = Date.now(); } State.variables.questBoard = qb; return qb; }>> <<run setup.ensureQuestBoardState()>> /* Settings */ <<set $settings to { "musicVolume": 0.5, "effectVolume": 0.5, "dialogueVolume": 0.5, "ambientVolume": 0.5 }>> /* Achievements */ <<set $achievements to { "unlocked": [], "points": 0 }>> /* Initialize character examination stats */ <<set $slaveExamStats = { /* Body stats */ "Frame": 0, "Pregnant": 0, "Nipples": 0, "Navel": 0, "Collar": 0, "Nipple Wear": 0, /* Vaginal Status */ "Vaginal State": 0, "Pubic Presence": 0, "Fertility": 0, "Pierced Clit": 0, "Vaginal Loads": 0, "Vaginal Wear": 0, /* Oral Status */ "Swallowed Semen": 0, "Pierced Tongue": 0, "Pierced Lips": 0, "Oral Loads": 0, "Oral Wear": 0, /* Anal Status */ "Anal State": 0, "Anal Plug": 0, "Back Tattoo": 0, "Anal Loads": 0, "Anal Wear": 0 }>> <<set $slaveStats = { "Strength": 20, "Dexterity": 20, "Intelligence": 20, "Willpower": 20, "Discipline": 20, "Kindness": 20, "Happiness": 20, "Beauty": 20, "Charisma": 20, "Health": 20, "Stamina": 20, "Affection": 20, /* Skills */ "Academics": 20, "Administration": 20, "Medicine": 20, "Science": 20, "Melee Combat": 20, "Ranged Combat": 20, "Domestic": 20, "Acrobatics": 20, "Dancing": 20, "Performance": 20, "Gardening": 20, "Survival": 20, /* States */ "Fear": 20, "Defiance": 20, "Stress": 20, "Respect": 20, "Corruption": 20, "Devotion": 20, /* Sexual Skills */ "Vaginal": 20, "Anal": 20, "Oral": 20, "Kissing": 20, "Lesbian": 20, "Arousal": 20 }>> /* Game State */ <<set $gameDays to 0>> <<set $gameDate to "">> /* Initialize settlement buildings storage */ <<set $settlementBuildings to {}>> /* Initialize areas for city builder - required for SugarCube save serialization */ <<set $areas to {}>> /* Add initialization function for settlement building system */ <<script>> // Make sure the building system is properly initialized State.variables.startConstruction = function(buildingType) { const definition = State.variables.buildingDefinitions[buildingType]; if (!definition) return false; // Check if we can afford it let totalGoldCost = 0; if (typeof definition.baseCost === 'object') { for (const [resource, amount] of Object.entries(definition.baseCost)) { if (resource === 'gold') { totalGoldCost = amount; } if (State.variables.resources && State.variables.resources[resource] < amount) { return false; // Can't afford } } } else if (typeof definition.baseCost === 'number') { totalGoldCost = definition.baseCost; if (State.variables.gold < totalGoldCost) { return false; // Can't afford } } // Deduct costs if (typeof definition.baseCost === 'object') { for (const [resource, amount] of Object.entries(definition.baseCost)) { if (resource === 'gold') { State.variables.gold -= amount; } else if (State.variables.resources) { State.variables.resources[resource] -= amount; } } } else if (typeof definition.baseCost === 'number') { State.variables.gold -= totalGoldCost; } // Create building instance if it doesn't exist if (!State.variables.settlementBuildings[buildingType]) { State.variables.settlementBuildings[buildingType] = { built: true, constructionComplete: false, constructionDaysLeft: definition.constructionTime, constructionHoursLeft: definition.constructionTime * 24, // Add hours for time system level: 1, workers: [], managers: [], currentOutput: 0 }; } else { // Update existing building State.variables.settlementBuildings[buildingType].built = true; State.variables.settlementBuildings[buildingType].constructionComplete = false; State.variables.settlementBuildings[buildingType].constructionDaysLeft = definition.constructionTime; State.variables.settlementBuildings[buildingType].constructionHoursLeft = definition.constructionTime * 24; } return true; }; State.variables.upgradeBuilding = function(buildingType) { const definition = State.variables.buildingDefinitions[buildingType]; const instance = State.variables.settlementBuildings[buildingType]; if (!definition || !instance || !instance.built || !instance.constructionComplete) { return false; } const nextLevel = instance.level + 1; const upgradeCost = definition.upgradeCost(instance.level); if (State.variables.gold < upgradeCost) { return false; // Can't afford } // Deduct costs State.variables.gold -= upgradeCost; // Set building to upgrading state instance.constructionComplete = false; const upgradeDays = Math.ceil(definition.constructionTime * 0.7); // Upgrade is faster than initial construction instance.constructionDaysLeft = upgradeDays; instance.constructionHoursLeft = upgradeDays * 24; // Set hours properly for time system instance.level = nextLevel; return true; }; State.variables.updateBuildings = function() { if (!State.variables.settlementBuildings) return; for (const [buildingType, instance] of Object.entries(State.variables.settlementBuildings)) { if (instance.built && !instance.constructionComplete) { // Decrement days by 1 instance.constructionDaysLeft -= 1; // Also properly handle hours if (instance.constructionHoursLeft === undefined) { // Initialize hours if missing (24 hours per day) instance.constructionHoursLeft = instance.constructionDaysLeft * 24; console.log(`Initialized constructionHoursLeft for ${buildingType} during daily update`); } else { // Decrement by 24 hours (1 day) instance.constructionHoursLeft = Math.max(0, instance.constructionHoursLeft - 24); } if (instance.constructionDaysLeft <= 0) { instance.constructionComplete = true; instance.constructionDaysLeft = 0; instance.constructionHoursLeft = 0; // Set initial output based on definition const definition = State.variables.buildingDefinitions[buildingType]; if (definition) { instance.currentOutput = definition.baseOutput; } console.log(`Construction of ${buildingType} complete!`); } } } }; <</script>>
[[Intro3]] :: TestStateVariables Current State Variables: First Name: <<print $firstName>> Last Name: <<print $lastName>> Settlement Name: <<print $settlementName>> Race: <<print $characterRace>> Attributes: <<for _key, _value range $attributes>> <<print _key>>: <<print _value>> <</for>> Selected Traits: <<if $selectedTraits.length > 0>> <<for _trait range $selectedTraits>> <<print _trait.name>> <</for>> <<else>> No traits selected <</if>> Backstory Choices: Family Background: <<print $familyBackground>> Childhood Trait: <<print $childhoodTrait>> Teen Activity: <<print $teenActivity>> Adult Achievement: <<print $adultAchievement>>
<<script>> AudioManager.stopVoice(); setTimeout(function() { AudioManager.playVoice('intro3'); }, 1200); <</script>> <div class="fullscreenbackground"> <div class="prince_looking_background"> <div class="prince_looking_container"> <div class="prince_looking_paragraph"> <p>Your life has been one of privilege, shaped by the fortune of your noble lineage. </p> <p>Blessed by birth, you have never known the hardships endured by the common folk. </p> <p>The Great War, though a grim shadow over the realm, remains a distant concern, as your troubles lie with rival factions seeking to claim your lands.</p> </div> </div> <div class="agree_button_3_1" onclick="goToPassage('Intro4')"> <p>Continue<div class="golden_arrow"></div></p> <<script>> AudioManager.playSFX('button_click'); <</script>> </div> </div> </div>
<<script>> if (window.__intro4VoiceTimer) { clearTimeout(window.__intro4VoiceTimer); window.__intro4VoiceTimer = null; } window.cancelIntro4VoiceTimer = function() { if (window.__intro4VoiceTimer) { clearTimeout(window.__intro4VoiceTimer); window.__intro4VoiceTimer = null; } }; AudioManager.stopVoice(); window.__intro4VoiceTimer = setTimeout(function() { window.__intro4VoiceTimer = null; if (window.AudioManager) { AudioManager.playVoice('intro4'); } }, 1200); $(document).one(':passagestart', function() { if (window.cancelIntro4VoiceTimer) { window.cancelIntro4VoiceTimer(); } }); <</script>> <div class="fullscreenbackground"> <center> <div class="gorgeous_ass_moon_background"> <div class="moon_container"> <div class="moon_paragraph"> <p>Tonight, after a long day, you lie within the comforts of your bedchamber, lulled by the tranquility of your surroundings. </p> <p>As sleep claims you, your dreams turn vivid, fixating on the newly hired servant girl, Named Bella, within your household. </p> </div> </div> <div class="agree_button_3_3" onclick="cancelIntro4VoiceTimer(); AudioManager.stopAmbient(); AudioManager.stopMusic(); AudioManager.stopVoice(); PrologueStoryEngine.startPrologue('prologue_root')"> <p> Continue <div class="golden_arrow_2"></div> </p> <<script>> AudioManager.playSFX('button_click'); <</script>> </div> </div> </center> </div>
<div class="fullscreenbackground"> <div class="worldmap"> <button class="map-return-btn" onclick="returnToPrevious()"> </button> <svg width="1000" height="750" viewBox="0 0 1000 750" xmlns="http://www.w3.org/2000/svg"> <image href="Bin/Contents/Locations/worldmap.jpg" width="100%" height="100%" /> <path d="M55 349.5V356L58.5 370L85 365.5L105.5 354.5L107 352L109 349.5L110 345L111.5 340V338H114.5V336H111.5V333H110L107 328L92 316.5L87 312.5L85 309L79 305.5L77 310.5H75L72 316.5L64 318.5L63 322L60.5 324.5L64 328L62 330L63 336L65 342L59.5 348.5L55 349.5Z" class="region" data-region="area1" /> </svg> </div> </div>
<div class="fullscreenbackground"> <div class="settings_background"> <p2>Settings</p2> <div class="background_panels"> <div class="panel panel1"> <!-- Audio Language Selector --> <div class="language-control"> <p class="settings-language-title">Audio Language</p> <select class="language-dropdown"> <option value="en">English</option> </select> </div> <!-- Music Volume Control --> <div class="volume-control"> <p>Music Volume</p> <div class="slider-container"> <div class="slider-fill"></div> <div class="slider-bar"> <img class="diamond-head" src="Bin/Contents/UI/Buttons/btn_settact.png" alt="handle"> </div> </div> <span class="volume-value">0%</span> </div> <!-- Dialogue with unique classes --> <div class="volume-control dialogue-control"> <p>Dialogue Volume</p> <div class="slider-container"> <div class="dialogue-fill slider-fill"></div> <div class="dialogue-bar slider-bar"> <img class="dialogue-head diamond-head" src="Bin/Contents/UI/Buttons/btn_settact.png" alt="handle"> </div> </div> <span class="dialogue-value volume-value">0%</span> </div> <!-- Sound Effect with unique classes --> <div class="volume-control effect-control"> <p>Sound Effect Volume</p> <div class="slider-container"> <div class="effect-fill slider-fill"></div> <div class="effect-bar slider-bar"> <img class="effect-head diamond-head" src="Bin/Contents/UI/Buttons/btn_settact.png" alt="handle"> </div> </div> <span class="effect-value volume-value">0%</span> </div> <!-- Ambient with unique classes --> <div class="volume-control ambient-control"> <p>Ambient Volume</p> <div class="slider-container"> <div class="ambient-fill slider-fill"></div> <div class="ambient-bar slider-bar"> <img class="ambient-head diamond-head" src="Bin/Contents/UI/Buttons/btn_settact.png" alt="handle"> </div> </div> <span class="ambient-value volume-value">0%</span> </div> </div> <div class="panel panel2"> <!-- Version Header --> <div class="settings-version-header"> <span>Version 0.1A</span> <span>(Public Build)</span> </div> <div class="settings-menu-buttons"> <button class="menu-button" onclick="returnToMain()"><p>Main Menu</p></button> <button class="menu-button" onclick="saveGame()"><p>Save Game</p></button> <button class="menu-button" onclick="goToPassage('credits')"><p>Credits</p></button> </div> <<nobr>> <div class="settings-supporter-container"> <div class="settings-supporter-header"><span>Support us on</span></div> <div class="settings-supporter-content"> <div class="settings-supporter-images"> <a href="https://www.patreon.com/c/Bemorian_Studios" target="_blank"> <div class="supporter-icon" id="supporter-patreon"></div> </a> <a href="https://subscribestar.adult/bemorian-studios" target="_blank"> <div class="supporter-icon" id="supporter-subscribestar"></div> </a> <a href="https://bemorian-studios.itch.io/wob" target="_blank"> <div class="supporter-icon" id="supporter-itchio"></div> </a> </div> <div class="settings-supporter-benefits"> <span><span class="highlight-red">Tier benefits</span> includes <span class="highlight-yellow">unique quest lines</span>, <span class="highlight-yellow">more customizable options</span>, and <span class="highlight-yellow">early access</span> release of the game</span> </div> </div> </div> <</nobr>> </div> <div class="panel panel3"></div> </div> <div class="bottom_settings_buttons"> <button class="settings_button" id="default_button" onclick="window.handleDefaultButton()"><p>Default</p></button> <button class="settings_button" id="apply_button" onclick="window.handleApplySettings()"><p>Apply</p></button> </div> </div> </div>
<<audio stop>>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_building">> <div class="wooden-panels-container"> <div class="wooden-panel left"> <div class="building-grid-panel"> <!-- Building Grid Container --> <div class="building-grid"> <!-- Dynamic Building Slots (generated by JavaScript) --> <div class="building-slots-container"> <!-- Slots will be generated here --> </div> <!-- Navigation --> <div class="grid-navigation"> <button class="nav-arrow left" onclick="window.prevBuildingPage()"></button> <span class="page-number">1</span> <button class="nav-arrow right" onclick="window.nextBuildingPage()"></button> </div> <button class="exit-btn" onclick="returnToPrevious()">Exit</button> </div> </div> </div> <div class="wooden-panel right"> <div class="building-details-panel"> <div class="building-info"> <div class="details-paper"> </div> </div> </div> </div> </div> </div> </div> <<script>> $(document).ready(function() { if (typeof State !== 'undefined' && State.variables) { State.variables.buildingManagementSourceArea = State.variables.currentArea || window.CityBuilder?.AreaManager?.currentArea || 'area1'; } window.settlementSystem(); if (State.variables && State.variables.pendingAssignment) { const pending = State.variables.pendingAssignment; State.variables.pendingAssignment = null; if (pending && pending.buildingId && typeof window.buildAssignMacro === 'function') { window.buildAssignMacro(pending.buildingId); if (pending.tab === 'workers' && typeof window.switchBuildingView === 'function') { window.switchBuildingView('workers-view'); } } } }); <</script>>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_player">> <div class="wooden-panels-container"> <div class="wooden-panel left"> <div class="character-info"> <div class="game-details"> <p>Game Days: <<print $gameTime.totalDays - ($playerState.identity.createdOnDay || 0)>></p> <p>Game Date: <<print $gameTime.day>></p> </div> <div class="character-portrait"> <img src="Bin\Contents\Characters\Human_Storyline\Story\Player\Frame.png" alt="Bishopp Harrington" class="portrait"/> </div> <h2 class="character-name"> <p><<print $playerState.identity.firstName>> <<print $playerState.identity.lastName>></p> <img src="Bin/Contents/UI/Containers/B_TitleName.png"/> </h2> <<nobr>> <div class="basic-info"> <<nobr>> <p>Wife: <<print $playerState.relationships.wife || "Unmarried">></p> <p>Age: <<print $playerState.identity.age>></p> <p>Title: <<print $playerState.identity.title || "Lord">></p> <p>Concubines: <<print $playerState.relationships.concubines>></p> <p>Sex: Male</p> <p>Influence: <<print $playerState.resources.influence>></p> <p>Children: <<print $playerState.relationships.children>></p> <</nobr>> </div> <</nobr>> <div class="stats"> <<nobr>> <div class="player-timeline"> <div class="timeline-header">Life's Journey</div> <div class="timeline-row"> <div class="timeline-stage" id="lj-family"> <div class="timeline-dot"></div> <div class="timeline-label">Family</div> <div class="timeline-value"><<print $playerState.background.family || "Unknown">></div> </div> <div class="timeline-stage" id="lj-childhood"> <div class="timeline-dot"></div> <div class="timeline-label">Childhood</div> <div class="timeline-value"><<print $playerState.background.childhood || "Unknown">></div> </div> <div class="timeline-stage" id="lj-teen"> <div class="timeline-dot"></div> <div class="timeline-label">Teen Years</div> <div class="timeline-value"><<print $playerState.background.teen || "Unknown">></div> </div> <div class="timeline-stage" id="lj-achievement"> <div class="timeline-dot"></div> <div class="timeline-label">Achievement</div> <div class="timeline-value"><<print $playerState.background.adult || "Unknown">></div> </div> </div> </div> <</nobr>> </div> <button class="exit-btn" onclick="returnToPrevious()">Exit</button> </div> </div> <div class="wooden-panel right"> <div class="skills-traits"> <div class="special-skills"> <h2>Special Skill</h2> <p>Choose 1 Special Combat Skill to bring into battles</p> <div class="skill-icons"> <div class="skill-icon"> <img src="Bin/Contents/Combat/Skills/HeroSkills/HS_Inspire.png" alt="Inspire"/> <span>Inspire</span> </div> <div class="skill-icon"> <img src="Bin/Contents/Combat/Skills/HeroSkills/HS_LastStand.png" alt="Last Stand"/> <span>Last Stand</span> </div> <div class="skill-icon"> <img src="Bin/Contents/Combat/Skills/Archer/A_RapidFire.png" alt="Rapid Fire"/> <span>Rapid Fire</span> </div> </div> </div> <div class="stats-traits-container"> <div class="column left-column"> <div class="pm-header">Skills</div> <<nobr>> <div class="skills-list"> <<set _skillsList = ["charisma", "strength", "dexterity", "constitution", "intelligence", "stamina", "martial", "diplomacy", "stewardship", "manipulate", "copulation", "kissing"]>> <<for _skill range _skillsList>> <div class="skill-row"> <span class="skill-name"><<print window.StatsManager.formatAttributeName(_skill)>></span> <span class="skill-value"><<print $playerState.stats.attributes[_skill] || 0>></span> </div> <</for>> </div> <</nobr>> </div> <div class="column right-column"> <div class="pm-header">Traits</div> <div class="traits-list-player">\ <<if $playerState.creation.selectedTraits && $playerState.creation.selectedTraits.length > 0>>\ <<for _trait range $playerState.creation.selectedTraits>>\ <span><<print _trait.name>></span>\ <</for>>\ <<else>>\ <span>No traits selected</span>\ <</if>>\ </div> </div> </div> <button class="inventory-btn" onclick="goToPassage('player_inventory')"> <p>Inventory</p> </button> </div> </div> </div> </div> </div>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_player">> <div class="wooden-panels-container"> <div class="wooden-panel left"> <<characterGrid>> <button class="exit-btn" onclick="returnToPrevious()">Exit</button> </div> <div class="wooden-panel right"> <<characterDetails>> </div> </div> </div> </div>
<div class="top-bars"> <div class="menu-bar"> <div class="menu-button-containers"> <button class="menu-btn" onclick="switchTab('player')">Player</button> <button class="menu-btn" onclick="switchTab('companion')">Companions</button> </div> </div> <<include "resources-main">> </div>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_player">> <div class="wooden-panels-container"> <div class="wooden-panel left"> <div class="army-tabs-container"> <div class="army-tab active" onclick="switchArmyTab('army')">Regiments</div> <div class="army-tab" onclick="switchArmyTab('captains')">Captains</div> </div> <<armyGrid>> </div> <div class="wooden-panel right"> <<armyDeployment>> </div> </div> </div> </div>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_player">> <div class="wooden-panels-container"> <div class="wooden-panel left"> <<characterGrid>> <button class="exit-btn" onclick="returnToPrevious()">Exit</button> </div> <div class="wooden-panel right"> <<actionTab>> </div> </div> </div> </div>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_player">> <div class="wooden-panels-container"> <div class="wooden-panel left"> <<characterGrid>> <button class="exit-btn" onclick="returnToPrevious()">Exit</button> </div> <div class="wooden-panel right"> <<examinationTab>> </div> </div> </div> </div>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_player">> <div class="wooden-panels-container"> <div class="wooden-panel left"> <<characterGrid>> <button class="exit-btn" onclick="returnToPrevious()">Exit</button> </div> <div class="wooden-panel right"> <<statsTraitsTab>> </div> </div> </div> </div>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_player">> <div class="wooden-panels-container"> <div class="wooden-panel left"> <<characterGrid>> <button class="exit-btn" onclick="returnToPrevious()">Exit</button> </div> <div class="wooden-panel right"> <<commandsTab>> </div> </div> </div> </div>
<div class="fullscreenbackground"> <div class="combat_gui_gametab"> <div class="combat_system_control_panel"> <!-- Army Units Container --> <div class="combat_system_container combat_system_army_container"> <!-- Unit 1: Bishop --> <div class="combat_system_unit_slot"> <div class="combat_system_unit_portrait"> <!-- Icon would go here --> </div> <div class="combat_system_unit_name"></div> <div class="combat_system_unit_stats"> </div> </div> <!-- Unit 2: Alexander --> <div class="combat_system_unit_slot"> <div class="combat_system_unit_portrait"> <!-- Icon would go here --> </div> <div class="combat_system_unit_name"></div> <div class="combat_system_unit_stats"> </div> </div> <!-- Unit 3: Jack --> <div class="combat_system_unit_slot"> <div class="combat_system_unit_portrait"> <!-- Icon would go here --> </div> <div class="combat_system_unit_name"></div> <div class="combat_system_unit_stats"> </div> </div> <!-- Unit 4: Heavy Cavalry --> <div class="combat_system_unit_slot"> <div class="combat_system_unit_portrait"> <!-- Icon would go here --> </div> <div class="combat_system_unit_name"></div> <div class="combat_system_unit_stats"> </div> </div> <!-- Unit 5: Light Cavalry --> <div class="combat_system_unit_slot"> <div class="combat_system_unit_portrait"> <!-- Icon would go here --> </div> <div class="combat_system_unit_name"></div> <div class="combat_system_unit_stats"> </div> </div> </div> <!-- Army Abilities Container --> <div class="combat_system_container combat_system_army_abilities_container"> <div class="combat_system_abilities_grid"> <div class="combat_system_ability_slot" data-slot-type="combat" data-slot-index="0"> <!-- Slot 1 (Major?) --> </div> <div class="combat_system_ability_slot" data-slot-type="combat" data-slot-index="1"> <!-- Slot 2 --> </div> <div class="combat_system_ability_slot" data-slot-type="combat" data-slot-index="2"> <!-- Slot 3 --> </div> <div class="combat_system_ability_slot" data-slot-type="combat" data-slot-index="3"> <!-- Slot 4 --> </div> <div class="combat_system_ability_slot" data-slot-type="combat" data-slot-index="4"> <!-- Slot 5 --> </div> <div class="combat_system_ability_slot" data-slot-type="combat" data-slot-index="5"> <!-- Slot 6 --> </div> <div class="combat_system_ability_slot" data-slot-type="combat" data-slot-index="6"> <!-- Slot 7 --> </div> <div class="combat_system_ability_slot" data-slot-type="combat" data-slot-index="7"> <!-- Slot 8 --> </div> </div> </div> <!-- Support Abilities Container --> <div class="combat_system_container combat_system_support_abilities_container"> <<nobr>> <div class="combat_system_support_grid"> <div class="combat_system_support_slot" data-slot-type="support" data-slot-index="0"> <!-- Support 1 --> </div> <div class="combat_system_support_slot" data-slot-type="support" data-slot-index="1"> <!-- Support 2 --> </div> <div class="combat_system_support_slot" data-slot-type="support" data-slot-index="2"> <!-- Support 3 --> </div> <div class="combat_system_support_slot" data-slot-type="support" data-slot-index="3"> <!-- Support 4 --> </div> </div> <</nobr>> </div> </div> </div> <div class="combat-template"> <div class="combat-board-mount"></div> </div> </div>
<div class="top-bars"> <div class="menu-bar"> <div class="menu-button-containers"> <button class="menu-btn" onclick="switchTab('settlement_overview')">Overview</button> <button class="menu-btn" onclick="switchTab('settlement')">Buildings</button> </div> </div> <<include "resources-main">> </div>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_building">> <div class="wooden-panels-container"> <div class="wooden-panel overview-left"> <div class="settlement-info-container"> <div class="settlement-name"> <h1><span id="current-area-name">Loading...</span></h1> </div> <div class="settlement-image"> <!-- Castle/settlement image container --> </div> <div class="settlement-alerts"> <div class="settlement-threats"> <h3 class="threats-header">Ongoing Threats:</h3> <p class="threats-details"> <<if $cityEvents && $cityEvents.length>> <<print $cityEvents[0].message>> <<else>> No immediate threats detected. <</if>> </p> </div> </div> <button class="exit-btn-overview" onclick="returnToPrevious()">Exit</button> </div> </div> <div class="wooden-panel right"> <div class="settlement-overview-grid"> <!-- First Row --> <div class="settlement-overview-row-1"> <div class="settlement-overview-state"> <div class="state-label">Happiness:</div> <div class="state-value-container"> <div class="state-value stat-clickable" onclick="window.showStatBreakdown('happiness')"><<print $cityStats.happiness>></div> </div> </div> <div class="settlement-overview-state"> <div class="state-label">Security:</div> <div class="state-value-container"> <div class="state-value stat-clickable" onclick="window.showStatBreakdown('security')"><<print $cityStats.security>></div> </div> </div> <div class="settlement-overview-state"> <div class="state-label">Citizens:</div> <div class="state-value-container"> <div class="state-value stat-clickable" onclick="window.showStatBreakdown('population')"><<print $resources.population>></div> </div> </div> </div> <div class="settlement-overview-row-2"> <div class="settlement-overview-state"> <div class="state-label">Loyalty:</div> <div class="state-value-container"> <div class="state-value stat-clickable" onclick="window.showStatBreakdown('loyalty')"><<print $cityStats.loyalty>></div> </div> </div> <div class="settlement-overview-state"> <div class="state-label">Crime:</div> <div class="state-value-container"> <div class="state-value stat-clickable" onclick="window.showStatBreakdown('crime')"><<print $cityStats.crime>></div> </div> </div> <div class="settlement-overview-state"> <div class="state-label">Slaves:</div> <div class="state-value-container"> <div class="state-value stat-clickable" onclick="window.showStatBreakdown('slaves')"><<print $cityStats.slaves>></div> </div> </div> </div> </div> <div class="settlemen-overview-details"> <div class="so-details-columns"> <div class="taxation-column"> <div class="taxation-column-header">Taxation</div> <div class="taxation-item"> <div class="role-icon question-mark"></div> <span>Advisor</span> </div> <div class="taxation-item"> <div class="role-icon question-mark"></div> <span>Advisor</span> </div> <div class="taxation-item"> <div class="role-icon question-mark"></div> <span>Advisor</span> </div> <div class="taxation-item"> <div class="role-icon question-mark"></div> <span>Advisor</span> </div> <div class="taxation-item"> <div class="role-icon question-mark"></div> <span>Advisor</span> </div> <div class="taxation-item"> <div class="role-icon question-mark"></div> <span>Advisor</span> </div> </div> <div class="food-column"> <div class="food-column-header">Food</div> <div class="taxation-item"> <div class="role-icon question-mark"></div> <span>Advisor</span> </div> <div class="taxation-item"> <div class="role-icon question-mark"></div> <span>Advisor</span> </div> <div class="taxation-item"> <div class="role-icon question-mark"></div> <span>Advisor</span> </div> </div> </div> </div> </div> </div> </div> </div> <script> // Wait for functions to load, then update window.waitAndUpdate = function() { if (typeof window.updateAreaNameDisplay === 'function') { window.updateAreaNameDisplay(); } else { // Function not loaded yet, try again setTimeout(window.waitAndUpdate, 100); } } window.waitAndUpdate(); // Update settlement image based on current events window.updateSettlementImageOnLoad = function() { if (typeof window.EventEngine !== 'undefined' && typeof window.EventEngine.updateSettlementImage === 'function') { const currentEvent = window.EventEngine.getCurrentEventWithImage(); window.EventEngine.updateSettlementImage(currentEvent); } else { setTimeout(window.updateSettlementImageOnLoad, 100); } } // Start image update window.updateSettlementImageOnLoad(); </script>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_player">> <div class="wooden-panels-container"> <div class="wooden-panel left"> <<slaveGrid>> <button class="exit-btn" onclick="returnToPrevious()">Exit</button> </div> <div class="wooden-panel right"> <<slaveTab>> </div> </div> </div> </div>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_player">> <div class="wooden-panels-container"> <div class="wooden-panel left"> <<slaveGrid>> <button class="exit-btn" onclick="returnToPrevious()">Exit</button> </div> <div class="wooden-panel right"> <<slaveExaminationTab>> </div> </div> </div> </div>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_player">> <div class="wooden-panels-container"> <div class="wooden-panel left"> <<slaveGrid>> <button class="exit-btn" onclick="returnToPrevious()">Exit</button> </div> <div class="wooden-panel right"> <<slaveStatsTraitsTab>> </div> </div> </div> </div>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_player">> <div class="wooden-panels-container"> <div class="wooden-panel left"> <<slaveGrid>> <button class="exit-btn" onclick="returnToPrevious()">Exit</button> </div> <div class="wooden-panel right"> <<slaveCommandsTab>> </div> </div> </div> </div>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_player">> <div class="wooden-panels-container"> <div class="wooden-panel left"> <<characterGrid>> <button class="exit-btn" onclick="returnToPrevious()">Exit</button> </div> <div class="wooden-panel right"> <<moveTab>> </div> </div> </div> </div>
<div class="fullscreenbackground"> <div class="inv-tab-inventory-background"> <<inventorySystem>> </div> </div>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_player">> <div class="wooden-panels-container"> <div class="wooden-panel left"> <div class="army-tabs-container"> <div class="army-tab active" onclick="switchArmyTab('army')">Regiments</div> <div class="army-tab" onclick="switchArmyTab('captains')">Captains</div> </div> <<captainGrid>> </div> <div class="wooden-panel right"> <<armyDeployment>> </div> </div> </div> </div>
<div class="fullscreenbackground"> <div class="player-inventory-container"> <<playerInventoryScreen>> </div> </div> <<initPlayerInventory>>
<div class="fullscreenbackground"> <div class="settlement-container"> <<include "UIHeader_building">> <div class="wooden-panels-container"> <div class="wooden-panel full-width"> <div class="building-tab-assign-container"> <div class="building-tab-macro-header"> <h2 id="building-name"></h2> <div class="building-tab-tabs"> <button class="building-tab-tab-btn active" id="tab-managers" onclick="switchBuildingTab('managers')"> Assigned Managers </button> <button class="building-tab-tab-btn" id="tab-workers" onclick="switchBuildingTab('workers')"> Assigned Workers </button> </div> </div> <div class="building-tab-assign-content" id="building-assign-content"> <!-- Content will be populated by JavaScript --> </div> <button class="building-tab-return-btn" onclick="Engine.play('Settlement')"> Return </button> </div> </div> </div> </div> </div> <<script>> $(document).ready(function() { // Get building data from state const buildingType = State.variables.selectedBuilding; const definition = State.variables.buildingDefinitions[buildingType]; const instance = State.variables.settlementBuildings[buildingType]; // Set building name in header document.getElementById('building-name').textContent = definition.name; // Set active tab let activeTab = 'managers'; // Function to render the tab content function renderBuildingTabContent() { const contentDiv = document.getElementById('building-assign-content'); // Determine maximum slots and current assignments based on tab const maxSlots = activeTab === 'managers' ? (definition.maxManagers || 2) : (definition.maxWorkers || 10); const assignedCount = activeTab === 'managers' ? (instance.managers ? instance.managers.length : 0) : (instance.workers ? instance.workers.length : 0); // Get all available companions const availableCompanions = State.variables.companions || []; // Generate HTML contentDiv.innerHTML = ` <div class="building-tab-slot-section"> <h3>${activeTab === 'managers' ? 'Assigned Manager' : 'Assigned Workers'}</h3> <div class="building-tab-slot-count">${assignedCount}/${maxSlots}</div> <div class="building-tab-assignment-slots"> ${generateBuildingTabSlots(maxSlots, activeTab, buildingType)} </div> </div> <div class="building-tab-available-section"> <h3>Available Companions</h3> <div class="building-tab-companion-list"> ${generateBuildingTabCompanionList(availableCompanions, activeTab)} </div> <div class="building-tab-scroll-controls"> <button class="building-tab-scroll-left">◄</button> <button class="building-tab-scroll-right">►</button> </div> </div> `; // Setup drag and drop after content is rendered setupBuildingTabDragAndDrop(); } // Function to switch between tabs window.switchBuildingTab = function(tab) { activeTab = tab; // Update active tab visual indicator document.querySelectorAll('.building-tab-tab-btn').forEach(btn => { btn.classList.remove('active'); }); document.getElementById('tab-' + tab).classList.add('active'); // Re-render content for the selected tab renderBuildingTabContent(); }; // Initial render renderBuildingTabContent(); }); <</script>>
<div class="fullscreenbackground"> <div class="settlement-container"> <div class="wooden-panels-container"> <div class="prison-panel left"> <div class="prison-header"> <span>Prisoners</span> </div> <div id="slave-roster-root" class="prison-roster-root"></div> <button class="prison-exit-btn" data-prison-action="exit">Exit</button> </div> <div class="prison-panel right"> <div class="guard-container"> <div class="guard left-guard"></div> <div class="guard right-guard"></div> </div> </div> </div> </div> </div> <script> (function() { if (window.RenderSlaveRoster) { window.RenderSlaveRoster('slave-roster-root'); } if (window.BindSlaveUI) { window.BindSlaveUI(); } })(); </script>
<div class="fullscreenbackground"> <div class="settlement-container"> <div class="wooden-panels-container" id="slave-detail-root"></div> </div> </div> <script> (function() { if (window.RenderSlaveDetail && window.SlaveSystemGetActive && !window.SlaveSystemGetActive()) { if (window.SlaveSystemGetRoster && window.SlaveSystemSetActive) { var roster = window.SlaveSystemGetRoster(); if (roster && roster.length > 0 && roster[0].rosterId) { window.SlaveSystemSetActive(roster[0].rosterId); } } } if (window.RenderSlaveDetail) { window.RenderSlaveDetail('slave-detail-root'); } if (window.BindSlaveUI) { window.BindSlaveUI(); } })(); </script>
<head> <meta charset="UTF-8" /> <title>World of Bemoria</title> <link rel="icon" type="image/x-icon" href="Bin/Contents/UI/Support/Icon.ico"> <meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="application-name" content="SugarCube" /> <meta name="version" content="2.37.3" /> <!-- existing head content will be here --> </head> <div id="start-screen" style=" position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: #000; z-index: 9999; display: flex; justify-content: center; align-items: center; cursor: pointer; "> <div id="start-text" style=" text-align: center; color: #fff; font-family: 'Times New Roman', serif; padding: 40px; "> <h1 style="font-size: 52px; margin-bottom: 20px;">Display Settings</h1> <p style="font-size: 22px; font-family: 'Courier New', monospace;"> <span class="key">Ctrl</span> + <span class="key">Scroll</span> or <span class="key">Ctrl</span> + <span class="key">+</span>/<span class="key">-</span> to adjust zoom </p> <p style="font-size: 18px; margin-top: 30px;"> Click anywhere to begin your journey... </p> </div> </div> <<script>> $(document).ready(function() { $('head').append(` <style> .key { display: inline-block; background: #333; border: 1px solid #666; border-radius: 4px; padding: 2px 8px; margin: 0 2px; font-family: 'Courier New', monospace; font-weight: bold; } </style> `); $('#start-screen').on('click', function() { $(this).fadeOut(500); setTimeout(function() { Engine.play("mainmenu"); }, 500); }); }); <</script>>
[script] /* This widget explicitly initializes the combat system */ if (window.initCombatSystem) { window.initCombatSystem(); } else { console.error("Combat system initialization function not found!"); }
[script] /* This widget explicitly cleans up the combat system */ if (window.cleanupCombatSystem) { window.cleanupCombatSystem(); } else { console.error("Combat system cleanup function not found!"); }
<div class="fullscreenbackground"> <center> <div class="character_creation"> <div class="left_menu_navigation_panel"> <div class="tab-buttons"> <button class="tab-button-1" data-tab="character" data-tooltip-placement="top" onclick="goToCCPassage('CharacterCreation')"></button> <button class="tab-button-2" data-tab="attributes" data-tooltip-placement="top" onclick="goToCCPassage('GodGamer')"></button> <button class="tab-button-3" data-tab="traits" data-tooltip-placement="top" onclick="goToCCPassage('SuperGamer')"></button> <button class="tab-button-4" data-tab="backstory" data-tooltip-placement="top" onclick="goToCCPassage('FuckingEasy')"></button> <button class="tab-button-5" data-tab="game_settings" data-tooltip-placement="top" onclick="goToCCPassage('KDAPlayer')"></button> </div> <div class="ca-info-title"><p>Attributes</p></div> <div class="attributes-panel"> <!-- First Row (existing) --> <div class="attributes-row"> <div class="attribute-pair"> <label>Strength</label> <div class="attribute-control"> <button class="decrease" onclick="adjustAttribute('strength', -1)"> <img src="Bin/Contents/UI/Buttons/btn_decrease.png" alt="decrease"> </button> <span id="strength-value" class="attribute-value" data-attr="strength">0</span> <button class="increase" onclick="adjustAttribute('strength', 1)"> <img src="Bin/Contents/UI/Buttons/btn_increase.png" alt="increase"> </button> </div> </div> <div class="attribute-pair"> <label>Dexterity</label> <div class="attribute-control"> <button class="decrease" onclick="adjustAttribute('dexterity', -1)"> <img src="Bin/Contents/UI/Buttons/btn_decrease.png" alt="decrease"> </button> <span id="dexterity-value" class="attribute-value" data-attr="dexterity">0</span> <button class="increase" onclick="adjustAttribute('dexterity', 1)"> <img src="Bin/Contents/UI/Buttons/btn_increase.png" alt="increase"> </button> </div> </div> </div> <!-- Second Row (existing) --> <div class="attributes-row"> <div class="attribute-pair"> <label>Stamina</label> <div class="attribute-control"> <button class="decrease" onclick="adjustAttribute('stamina', -1)"> <img src="Bin/Contents/UI/Buttons/btn_decrease.png" alt="decrease"> </button> <span id="stamina-value" class="attribute-value" data-attr="stamina">0</span> <button class="increase" onclick="adjustAttribute('stamina', 1)"> <img src="Bin/Contents/UI/Buttons/btn_increase.png" alt="increase"> </button> </div> </div> <div class="attribute-pair"> <label>Intelligence</label> <div class="attribute-control"> <button class="decrease" onclick="adjustAttribute('intelligence', -1)"> <img src="Bin/Contents/UI/Buttons/btn_decrease.png" alt="decrease"> </button> <span id="intelligence-value" class="attribute-value" data-attr="intelligence">0</span> <button class="increase" onclick="adjustAttribute('intelligence', 1)"> <img src="Bin/Contents/UI/Buttons/btn_increase.png" alt="increase"> </button> </div> </div> </div> <!-- Third Row --> <div class="attributes-row"> <div class="attribute-pair"> <label>Stewardship</label> <div class="attribute-control"> <button class="decrease" onclick="adjustAttribute('stewardship', -1)"> <img src="Bin/Contents/UI/Buttons/btn_decrease.png" alt="decrease"> </button> <span id="stewardship-value" class="attribute-value" data-attr="stewardship">0</span> <button class="increase" onclick="adjustAttribute('stewardship', 1)"> <img src="Bin/Contents/UI/Buttons/btn_increase.png" alt="increase"> </button> </div> </div> <div class="attribute-pair"> <label>Constitution</label> <div class="attribute-control"> <button class="decrease" onclick="adjustAttribute('constitution', -1)"> <img src="Bin/Contents/UI/Buttons/btn_decrease.png" alt="decrease"> </button> <span id="constitution-value" class="attribute-value" data-attr="constitution">0</span> <button class="increase" onclick="adjustAttribute('constitution', 1)"> <img src="Bin/Contents/UI/Buttons/btn_increase.png" alt="increase"> </button> </div> </div> </div> <!-- Fourth Row --> <div class="attributes-row"> <div class="attribute-pair"> <label>Diplomacy</label> <div class="attribute-control"> <button class="decrease" onclick="adjustAttribute('diplomacy', -1)"> <img src="Bin/Contents/UI/Buttons/btn_decrease.png" alt="decrease"> </button> <span id="diplomacy-value" class="attribute-value" data-attr="diplomacy">0</span> <button class="increase" onclick="adjustAttribute('diplomacy', 1)"> <img src="Bin/Contents/UI/Buttons/btn_increase.png" alt="increase"> </button> </div> </div> <div class="attribute-pair"> <label>Martial</label> <div class="attribute-control"> <button class="decrease" onclick="adjustAttribute('martial', -1)"> <img src="Bin/Contents/UI/Buttons/btn_decrease.png" alt="decrease"> </button> <span id="martial-value" class="attribute-value" data-attr="martial">0</span> <button class="increase" onclick="adjustAttribute('martial', 1)"> <img src="Bin/Contents/UI/Buttons/btn_increase.png" alt="increase"> </button> </div> </div> </div> <!-- Fifth Row --> <div class="attributes-row"> <div class="attribute-pair"> <label>Manipulate</label> <div class="attribute-control"> <button class="decrease" onclick="adjustAttribute('manipulate', -1)"> <img src="Bin/Contents/UI/Buttons/btn_decrease.png" alt="decrease"> </button> <span id="manipulate-value" class="attribute-value" data-attr="manipulate">0</span> <button class="increase" onclick="adjustAttribute('manipulate', 1)"> <img src="Bin/Contents/UI/Buttons/btn_increase.png" alt="increase"> </button> </div> </div> <div class="attribute-pair"> <label>Copulation</label> <div class="attribute-control"> <button class="decrease" onclick="adjustAttribute('copulation', -1)"> <img src="Bin/Contents/UI/Buttons/btn_decrease.png" alt="decrease"> </button> <span id="copulation-value" class="attribute-value" data-attr="copulation">0</span> <button class="increase" onclick="adjustAttribute('copulation', 1)"> <img src="Bin/Contents/UI/Buttons/btn_increase.png" alt="increase"> </button> </div> </div> </div> <!-- Sixth Row --> <div class="attributes-row"> <div class="attribute-pair"> <label>Charisma</label> <div class="attribute-control"> <button class="decrease" onclick="adjustAttribute('charisma', -1)"> <img src="Bin/Contents/UI/Buttons/btn_decrease.png" alt="decrease"> </button> <span id="charisma-value" class="attribute-value" data-attr="charisma">0</span> <button class="increase" onclick="adjustAttribute('charisma', 1)"> <img src="Bin/Contents/UI/Buttons/btn_increase.png" alt="increase"> </button> </div> </div> <div class="attribute-pair"> <label>Kissing</label> <div class="attribute-control"> <button class="decrease" onclick="adjustAttribute('kissing', -1)"> <img src="Bin/Contents/UI/Buttons/btn_decrease.png" alt="decrease"> </button> <span id="kissing-value" class="attribute-value" data-attr="kissing">0</span> <button class="increase" onclick="adjustAttribute('kissing', 1)"> <img src="Bin/Contents/UI/Buttons/btn_increase.png" alt="increase"> </button> </div> </div> </div> </div> </div> <div class="ca-character-preview"> <video autoplay loop muted> <source src="Bin/Contents/Scenes/Human/Character_Creation/Idle.webm" type="video/webm"> </video> </div> <div class="summary_panel"> <div class="summary_container"> <span class="summary-title">Summary</span> <div class="points-section"> <p>Points Remaining: <span id="points-remaining">35</span></p> </div> <div class="attributes-section"> <p>Attributes:</p> <div id="attributes-summary"> <!-- Attributes will be populated here --> </div> </div> <div class="traits-section"> <p>Traits:</p> <div id="traits-summary"> <!-- Selected traits will be populated here --> </div> </div> <div class="traits-section"> <p>Traits:</p> <div id="traits-summary"> <!-- Selected traits will be populated here --> </div> </div> </div> <div class="swap_button" onclick="toggleSummaryView()"> </div> <div class="agree_button_4" onclick="validateAndProceed()"> <p>Proceed</p> </div> </div> </div> </center> </div>
<div class="fullscreenbackground"> <center> <div class="character_creation"> <div class="left_menu_navigation_panel"> <div class="tab-buttons"> <button class="tab-button-1" data-tab="character" data-tooltip-placement="top" onclick="goToCCPassage('CharacterCreation')"></button> <button class="tab-button-2" data-tab="attributes" data-tooltip-placement="top" onclick="goToCCPassage('GodGamer')"></button> <button class="tab-button-3" data-tab="traits" data-tooltip-placement="top" onclick="goToCCPassage('SuperGamer')"></button> <button class="tab-button-4" data-tab="backstory" data-tooltip-placement="top" onclick="goToCCPassage('FuckingEasy')"></button> <button class="tab-button-5" data-tab="game_settings" data-tooltip-placement="top" onclick="goToCCPassage('KDAPlayer')"></button> </div> <div class="ca-info-title"><p>Traits</p></div> <div class="traits-list"> <div class="trait-item"> <span class="trait-name">Attractive (-10)</span> <span class="info-icon" data-description="Your visage is most elegant, a rare beauty that captivate the hearts of the opposite sex. 20% chance to score a critical Charm/Manipulation roll when interacting with female NPCs in Seduction, Sexual Persuasion, or Declare Love situations. Gain a +10 Charm bonus.">INFO?</span> <button class="tick-button" onclick="selectTrait('Attractive', -10)"></button> </div> <div class="trait-item"> <span class="trait-name">Charismatic Leader (-10)</span> <span class="info-icon" data-description="Your presence inspires loyalty and confidence among your followers. Troop morale decreases 30% slower during battles or setbacks. NPC allies are 15% more likely to accept risky plans or negotiations. Gain +10 to Leadership rolls.">INFO?</span> <button class="tick-button" onclick="selectTrait('Charismatic Leader', -10)"></button> </div> <div class="trait-item"> <span class="trait-name">Brilliant Tactician (-10)</span> <span class="info-icon" data-description="Your strategic mind makes you a formidable opponent on the battlefield. Gain +15 to Combat Strategy rolls. Your troops deal 10% more damage during battles.">INFO?</span> <button class="tick-button" onclick="selectTrait('Brilliant Tactician', -10)"></button> </div> <div class="trait-item"> <span class="trait-name">Ambitious Visionary (-10)</span> <span class="info-icon" data-description="You dream big and aim to leave a mark on history. Gain +5% resource production across your settlements and +5 Influence gain per month. NPC rulers are 5% more likely to agree to alliances or treaties.">INFO?</span> <button class="tick-button" onclick="selectTrait('Ambitious Visionary', -10)"></button> </div> <div class="trait-item"> <span class="trait-name">Silver Tongue (-10)</span> <span class="info-icon" data-description="Your mastery of words can sway even the most stubborn minds. Gain +15 to Diplomacy and Persuasion rolls. You have a 10% chance to receive a critical success when negotiating peace treaties, trade deals, or forging alliances.">INFO?</span> <button class="tick-button" onclick="selectTrait('Silver Tongue', -10)"></button> </div> <div class="trait-item"> <span class="trait-name">Wealthy Heir (-15)</span> <span class="info-icon" data-description="You inherited not just land but vast treasures from your family. Start the game with 10,000 gold and +10% tax income. However, your fame draws opportunists who may seek to exploit your wealth.">INFO?</span> <button class="tick-button" onclick="selectTrait('Wealthy Heir', -15)"></button> </div> <div class="trait-item"> <span class="trait-name">Ruthless (-10)</span> <span class="info-icon" data-description="You do whatever it takes to achieve your goals, regardless of morality. Gain +10 to Intimidation rolls and +10% troop damage. NPCs may fear you, increasing compliance but reducing loyalty over time.">INFO?</span> <button class="tick-button" onclick="selectTrait('Ruthless', -10)"></button> </div> <div class="trait-item"> <span class="trait-name">Kindhearted (-10)</span> <span class="info-icon" data-description="Your compassion and generosity inspire admiration and trust. NPC loyalty increases 20% faster, and townsfolk are more likely to share secrets or offer help. However, you lose -5 Intimidation rolls due to your gentle demeanor.">INFO?</span> <button class="tick-button" onclick="selectTrait('Kindhearted', -10)"></button> </div> <div class="trait-item"> <span class="trait-name">Orator (-10)</span> <span class="info-icon" data-description="Your speeches can ignite passion or bring peace to the most chaotic situations. Gain +10 to Speech rolls and reduce war fatigue in your kingdom by 20%.">INFO?</span> <button class="tick-button" onclick="selectTrait('Orator', -10)"></button> </div> <div class="trait-item"> <span class="trait-name">Bloodthirsty (-15)</span> <span class="info-icon" data-description="Your thirst for blood drives you in battle and strikes fear in your enemies. Gain +15 to Combat rolls and 5% extra loot from defeated enemies. However, NPC reputation decreases 10% over time due to your violent tendencies.">INFO?</span> <button class="tick-button" onclick="selectTrait('Bloodthirsty', -15)"></button> </div> </div> <div class="navigation-arrow-pair"> [img[Bin/Contents/UI/Buttons/btn_arrowright.png][SuperGamer2]] </div> </div> <div class="ca-character-preview"> <video autoplay loop muted> <source src="Bin/Contents/Scenes/Human/Character_Creation/Idle.webm" type="video/webm"> </video> </div> <div class="summary_panel"> <div class="summary_container"> <span class="summary-title">Summary</span> <div class="points-section"> <p>Points Remaining: <span id="points-remaining">35</span></p> </div> <div class="attributes-section"> <p>Attributes:</p> <div id="attributes-summary"> <!-- Attributes will be populated here --> </div> </div> <div class="traits-section"> <p>Traits:</p> <div id="traits-summary"> <!-- Selected traits will be populated here --> </div> </div> </div> <div class="swap_button" onclick="toggleSummaryView()"> </div> <div class="agree_button_4" onclick="validateAndProceed()"> <p>Proceed</p> </div> </div> </div> </center> </div>
<div class="fullscreenbackground"> <center> <div class="character_creation"> <div class="left_menu_navigation_panel"> <div class="tab-buttons"> <button class="tab-button-1" data-tab="character" data-tooltip-placement="top" onclick="goToCCPassage('CharacterCreation')"></button> <button class="tab-button-2" data-tab="attributes" data-tooltip-placement="top" onclick="goToCCPassage('GodGamer')"></button> <button class="tab-button-3" data-tab="traits" data-tooltip-placement="top" onclick="goToCCPassage('SuperGamer')"></button> <button class="tab-button-4" data-tab="backstory" data-tooltip-placement="top" onclick="goToCCPassage('FuckingEasy')"></button> <button class="tab-button-5" data-tab="game_settings" data-tooltip-placement="top" onclick="goToCCPassage('KDAPlayer')"></button> </div> <div class="ca-info-title"><p>Traits</p></div> <div class="traits-list"> <div class="trait-item"> <span class="trait-name">Stoic (-5)</span> <span class="info-icon" data-description="You remain calm and composed even in the face of adversity. Gain +10 to Stress resistance and Leadership rolls. NPCs view you as dependable and wise.">INFO?</span> <button class="tick-button" onclick="selectTrait('Stoic', -5)"></button> </div> <div class="trait-item"> <span class="trait-name">Domineering(-10)</span> <span class="info-icon" data-description="Your commanding aura demands submission and obedience. Gain +15 to rolls involving Domination or Control when training slaves. Your slaves are 20% less likely to resist commands, but NPCs with a moral alignment may judge you harshly.">INFO?</span> <button class="tick-button" onclick="selectTrait('Domineering Presence', -10)"></button> </div> <div class="trait-item"> <span class="trait-name">Sadistic Nature (-15)</span> <span class="info-icon" data-description="You derive satisfaction from asserting your power over others, often through pain or humiliation. Gain +10 to Discipline rolls when breaking disobedient slaves. Slaves trained under you gain +5 to loyalty after submission, but lose -10 to happiness. NPCs with strong moral compasses dislike you.">INFO?</span> <button class="tick-button" onclick="selectTrait('Sadistic Nature', -15)"></button> </div> <div class="trait-item"> <span class="trait-name">Manipulator (-10)</span> <span class="info-icon" data-description="Your ability to mix seduction with manipulation leaves others defenseless to your whims. Gain +10 to Charm and Manipulation rolls when interacting with slaves or lower-ranked NPCs. You also unlock unique seductive dialogue options for breaking resistance.">INFO?</span> <button class="tick-button" onclick="selectTrait('Seductive Manipulator', -10)"></button> </div> <div class="trait-item"> <span class="trait-name">Cruel Trainer (-15)</span> <span class="info-icon" data-description="You believe harsh methods lead to the most obedient subjects. Training slaves is 30% faster, and they gain a +10 bonus to Discipline upon completion. However, they are more prone to rebellion if treated too kindly afterward.">INFO?</span> <button class="tick-button" onclick="selectTrait('Cruel Trainer', -15)"></button> </div> <div class="trait-item"> <span class="trait-name">Expert Tamer (-20)</span> <span class="info-icon" data-description="You are an unmatched master of breaking wills and reshaping individuals into loyal subjects. Unlock advanced techniques for slave training. Gain +10 to all training-related rolls and 10% faster training time. Slaves under your care are less likely to rebel but may become overly dependent on you.">INFO?</span> <button class="tick-button" onclick="selectTrait('Expert Tamer', -20)"></button> </div> <div class="trait-item"> <span class="trait-name">Forbidden Allure (-10)</span> <span class="info-icon" data-description="Your captivating beauty and charm blur the lines between fear and desire. Gain +20 to Seduction rolls when interacting with slaves or subjects. They are 15% more likely to develop a strong attachment or devotion to you, reducing resistance to commands.">INFO?</span> <button class="tick-button" onclick="selectTrait('Forbidden Allure', -10)"></button> </div> <div class="trait-item"> <span class="trait-name">Master of Chains (-15)</span> <span class="info-icon" data-description="Your proficiency in restraints and discipline ensures obedience at all costs. Gain +10 to rolls involving physical control or restraining slaves. Escape attempts are 25% less likely to succeed. NPCs may fear or respect your methods, depending on their alignment.">INFO?</span> <button class="tick-button" onclick="selectTrait('Master of Chains', -15)"></button> </div> <div class="trait-item"> <span class="trait-name">Corruptor (-10)</span> <span class="info-icon" data-description="You have a knack for reshaping minds, bending even the virtuous to darker paths. Gain +15 to Mental Manipulation rolls when breaking strong-willed slaves or converting NPCs. Resistance from moral characters decreases by 10%.">INFO?</span> <button class="tick-button" onclick="selectTrait('Corruptor', -10)"></button> </div> <div class="trait-item"> <span class="trait-name">Depraved Tyrant (-30)</span> <span class="info-icon" data-description="Your insatiable desire for control knows no bounds, pushing others into absolute submission. Unlock unique dialogue options for cruel or extreme training scenarios. Gain +10 to Control and Discipline rolls, and your reputation as a feared ruler spreads, increasing intimidation but lowering respect among moral NPCs.">INFO?</span> <button class="tick-button" onclick="selectTrait('Depraved Tyrant', -30)"></button> </div> </div> <div class="navigation-arrow-pair"> [img[Bin/Contents/UI/Buttons/btn_arrowleft.png][SuperGamer]] </div> </div> <div class="ca-character-preview"> <video autoplay loop muted> <source src="Bin/Contents/Scenes/Human/Character_Creation/Idle.webm" type="video/webm"> </video> </div> <div class="summary_panel"> <div class="summary_container"> <span class="summary-title">Summary</span> <div class="points-section"> <p>Points Remaining: <span id="points-remaining">35</span></p> </div> <div class="attributes-section"> <p>Attributes:</p> <div id="attributes-summary"> <!-- Attributes will be populated here --> </div> </div> <div class="traits-section"> <p>Traits:</p> <div id="traits-summary"> <!-- Selected traits will be populated here --> </div> </div> </div> <div class="swap_button" onclick="toggleSummaryView()"> </div> <div class="agree_button_4" onclick="validateAndProceed()"> <p>Proceed</p> </div> </div> </div> </center> </div>
<div class="fullscreenbackground"> <center> <div class="character_creation"> <div class="left_menu_navigation_panel"> <div class="tab-buttons"> <button class="tab-button-1" data-tab="character" data-tooltip="Origins" data-tooltip-placement="top" onclick="goToCCPassage('CharacterCreation')"></button> <button class="tab-button-2" data-tab="attributes" data-tooltip="Attributes" data-tooltip-placement="top" onclick="goToCCPassage('GodGamer')"></button> <button class="tab-button-3" data-tab="traits" data-tooltip="Skills" data-tooltip-placement="top" onclick="goToCCPassage('SuperGamer')"></button> <button class="tab-button-4" data-tab="backstory" data-tooltip="Legacy" data-tooltip-placement="top" onclick="goToCCPassage('FuckingEasy')"></button> <button class="tab-button-5" data-tab="game_settings" data-tooltip="Coat of Arms" data-tooltip-placement="top" onclick="goToCCPassage('KDAPlayer')"></button> </div> <div class="ca-info-title"><p>Legacy</p></div> <div class="backstory-section"> <!-- First prompt (existing) --> <div class="backstory-prompt"> <p> You were born into a Family of...</p> <button class="backstory-button" onclick="showBackstoryChoices(1)"> <img src="Bin/Contents/UI/Containers/CC_input_legacy.png" alt="Make your choice"> <span class="choice-text" id="familyChoice"></span> </button> </div> <!-- Second prompt --> <div class="backstory-prompt"> <p> As a Child you were noted for...</p> <button class="backstory-button" onclick="showBackstoryChoices(2)"> <img src="Bin/Contents/UI/Containers/CC_input_legacy.png" alt="Make your choice"> <span class="choice-text" id="childhoodChoice"></span> </button> </div> <!-- Third prompt --> <div class="backstory-prompt"> <p> As a teen you spend your free time...</p> <button class="backstory-button" onclick="showBackstoryChoices(3)"> <img src="Bin/Contents/UI/Containers/CC_input_legacy.png" alt="Make your choice"> <span class="choice-text" id="teenChoice"></span> </button> </div> <!-- Fourth prompt --> <div class="backstory-prompt"> <p> Your greatest achievement was....</p> <button class="backstory-button" onclick="showBackstoryChoices(4)"> <img src="Bin/Contents/UI/Containers/CC_input_legacy.png" alt="Make your choice"> <span class="choice-text" id="achievementChoice"></span> </button> </div> </div> </div> <div class="ca-character-preview"> <video autoplay loop muted> <source src="Bin/Contents/Scenes/Human/Character_Creation/Idle.webm" type="video/webm"> </video> </div> <div class="summary_panel"> <div class="summary_container"> <span class="summary-title">Summary</span> <div class="points-section"> <p>Points Remaining: <span id="points-remaining">35</span></p> </div> <div class="attributes-section"> <p>Attributes:</p> <div id="attributes-summary"> <!-- Attributes will be populated here --> </div> </div> <div class="traits-section"> <p>Traits:</p> <div id="traits-summary"> <!-- Selected traits will be populated here --> </div> </div> </div> <div class="swap_button" onclick="toggleSummaryView()"> </div> <div class="agree_button_4" onclick="validateAndProceed()"> <p>Proceed</p> </div> </div> </div> </center> </div>
<div class="fullscreenbackground"> <center> <div class="character_creation"> <div class="left_menu_navigation_panel"> <div class="tab-buttons"> <button class="tab-button-1" data-tab="character" data-tooltip-placement="top" onclick="goToCCPassage('CharacterCreation')"></button> <button class="tab-button-2" data-tab="attributes" data-tooltip-placement="top" onclick="goToCCPassage('GodGamer')"></button> <button class="tab-button-3" data-tab="traits" data-tooltip-placement="top" onclick="goToCCPassage('SuperGamer')"></button> <button class="tab-button-4" data-tab="backstory" data-tooltip-placement="top" onclick="goToCCPassage('FuckingEasy')"></button> <button class="tab-button-5" data-tab="game_settings" data-tooltip-placement="top" onclick="goToCCPassage('KDAPlayer')"></button> </div> <div class="ca-info-title"><p>Coat of Arms</p></div> <div class="coa-customizer"> <<nobr>> <div class="coa-frame" id="coaFrame"> <div class="coa-navArrow left" id="coaPolePrev"></div> <div class="coa-navArrow right" id="coaPoleNext"></div> <div class="coa-previewWrap"> <img id="coaPresetLayer" class="coa-layer coa-preset-layer"> </div> <div class="coa-presetMode" id="coaPresetMode"> <div class="coa-preset-label" id="coaPresetLabel">Preset 1</div> <button class="coa-preset-btn" id="coaCustomizeBtn" type="button">Customize</button> <div class="coa-tier-note"><span>Coat of Arms Customization</span> is a Tier Benefit Feature</div> </div> </div> <</nobr>> </div> </div> <div class="ca-character-preview"> <video autoplay loop muted> <source src="Bin/Contents/Scenes/Human/Character_Creation/Idle.webm" type="video/webm"> </video> </div> <div class="summary_panel"> <div class="summary_container"> <span class="summary-title">Summary</span> <div class="points-section"> <p>Points Remaining: <span id="points-remaining">35</span></p> </div> <div class="attributes-section"> <p>Attributes:</p> <div id="attributes-summary"> <!-- Attributes will be populated here --> </div> </div> <div class="traits-section"> <p>Traits:</p> <div id="traits-summary"> <!-- Selected traits will be populated here --> </div> </div> </div> <div class="swap_button" onclick="toggleSummaryView()"> </div> <div class="agree_button_4" onclick="validateAndProceed()"> <p>Proceed</p> </div> </div> </div> </center> </div>
<div class="right_container"> <!-- Top row --> <div class="top_row"> <button class="icon_button" onclick="openUIScreen('settlement_overview')"> <span class="tooltip">Castle Management</span> <img src="Picx/Contents/Buttons/btn_city.png" alt="Castle"> </button> <button class="icon_button" onclick="openUIScreen('player')"> <img src="Picx/Contents/Buttons/btn_companion.png" alt="Companion"> </button> <button class="icon_button" onclick="openUIScreen('army')"> <img src="Picx/Contents/Buttons/btn_army.png" alt="Army"> </button> <button class="icon_button"> <img src="Picx/Contents/Buttons/btn_quest.png" alt="Questbook"> </button> </div> <!-- Bottom row --> <div class="bottom_row"> <button class="icon_button" onclick="openWorldMap()"> <img src="Picx/Contents/Buttons/btn_map.png" alt="Map"> </button> <button class="icon_button" onclick="openUIScreen('game_prison')"> <img src="Picx/Contents/Buttons/btn_dungeon.png" alt="Prisoner"> </button> <button class="icon_button" onclick="openUIScreen('player_inventory')"> <img src="Picx/Contents/Buttons/btn_inventory.png" alt="Inventory"> </button> <button class="icon_button"> <img src="Picx/Contents/Buttons/btn_message.png" alt="Randomrequest"> </button> </div> </div> <div class="left_container"> <div class="basic_information"> <div class="character_icon"> <img src="Picx/Contents/Icons/ProfileHuman.png" alt="human_avatar"> </div> <div class="character_status"> <div class="status_panel"> <div class="status_header">Current State</div> <div class="status_info"> <div class="status_row"> <span class="status_bullet">•</span> <span class="status_label">Normal Health</span> <span class="status_bullet">•</span> <span class="status_label">Dirty</span> </div> <div class="status_row"> <span class="status_bullet">•</span> <span class="status_label">Hungry</span> <span class="status_bullet">•</span> <span class="status_label">Satisfied</span> </div> <div class="resources_row"> <div class="resource_item"> <img src="Picx/Contents/GUI/Icons/Icon_ResourceFaith.png" alt="faith"> <span class="resource_value">500k</span> </div> <div class="resource_item"> <img src="Picx/Contents/GUI/Icons/Icon_ResourceInfluence.png" alt="influence"> <span class="resource_value">500k</span> </div> </div> </div> </div> </div> </div> <div class="left_container_buttons"> <button class="left_container_button" onclick="openSettings()"> <img src="Picx/Contents/Buttons/sbtn_settings.png" alt="settings"> </button> <button class="left_container_button" onclick="saveGame()"> <img src="Picx/Contents/Buttons/sbtn_save.png" alt="save"> </button> <div class="return_home"> <button class="left_container_button"> <img src="Picx/Contents/Buttons/sbtn_rhome.png" alt="return_home"> </button> </div> <button class="left_container_button" onclick="window.openCalendar(); return false;"> <img src="Picx/Contents/Buttons/sbtn_skiptime.png" alt="time_skip"> </button> <div class="time_and_money"> <div class="dynamic_time_money"> <span class="time_value">12:00 AM | Monday</span> </div> </div> </div> </div>
<div class="resources-bar"> <div class="resource"><img src="Bin/Contents/UI/Icons/Icon_ResourceFood.png" alt=""/><<print $resources.food>></div> <div class="resource"><img src="Bin/Contents/UI/Icons/Icon_ResourceWood.png" alt=""/><<print $resources.wood>></div> <div class="resource"><img src="Bin/Contents/UI/Icons/Icon_ResourceIron.png" alt=""/><<print $resources.gold>></div> <div class="resource"><img src="Bin/Contents/UI/Icons/Icon_ResourceStone.png" alt=""/><<print $resources.stone>></div> <div class="resource"><img src="Bin/Contents/UI/Icons/Icon_ResourceGold.png" alt=""/><<print $resources.coins>></div> <div class="resource"><img src="Bin/Contents/UI/Icons/Icon_ResourcePopulation.png" alt=""/><<print $resources.population>></div> </div>
<div class="fullscreenbackground quest-ui-overlay" id="quest-ui-root" aria-hidden="true"> <div class="quest-ui-background"> <div class="quest-ui-header"><p>Quests</p></div> <div class="quest-ui-container"> <div class="quest-ui-content-header"> </div> <div class="quest-ui-main-content"> <div class="quest-ui-description"> </div> <div class="quest-ui-quest-guide"> <div class=quest-ui-tgbox></div> <div class="quest-ui-qm-desc"></div> </div> <div class="quest-ui-obj"> <p>Objective</p> </div> <div class="quest-ui-obj-list"> <ul class="quest-list"> </ul> </div> </div> <div class="quest-ui-left-container"> <div class="quest-ui-active-quest"> <div class="left-quest-list"> </div> <div class="left-quest-list left-quest-list--queued"> </div> <div class="left-quest-list left-quest-list--empty"> <p>No active citizen quests.</p> </div> </div> <div class="quest-ui-completed"> <div class="left-quest-cq"> </div> <div class="left-quest-cq"> </div> <div class="left-quest-cq left-quest-cq--empty"> <p>No completed quests yet.</p> </div> </div> </div> </div> <button class="quest-ui-close-btn"></button> </div> </div> <<script>> $(document).ready(function() { if (window.QuestTrackingSystem && typeof window.QuestTrackingSystem.render === 'function') { window.QuestTrackingSystem.render('#quest-ui-root'); } }); <</script>>
<<set $citizenDatabase = [ { id: "citizen_001", name: "Thomas", lastName: "Baker", portrait: "Bin/Contents/UI/Icons/Icon_MaleCaptured.png", stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 1, Willpower: 2, Discipline: 2, Kindness: 1, Happiness: 2, Beauty: 1, Charisma: 2, Health: 10, Stamina: 9, Affection: 0 }, skills: { Academics: 1, Administration: 1, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 1 }, states: { Fear: 2, Defiance: 6, Stress: 4, Respect: 14, Corruption: 1, Devotion: 8 } }, social: { relationshipStatus: "married", partnerId: "citizen_002", children: [], traits: ["Diligent", "Optimistic"], familyId: "family_young_couple" }, inventory: [ { id: 'bread_loaf', itemId: 'bread_loaf', name: 'Bread Loaf', quantity: 5 }, { id: 'wheat_bread', itemId: 'wheat_bread', name: 'Wheat Bread', quantity: 3 }, { id: 'utility_knife', itemId: 'utility_knife', name: 'Utility Knife' } ], goldPool: 28, foodPool: 35, quests: [ "quest_thomas_bandits", "task_gather_mushrooms" ], itemPreferences: { byItem: { 'bread_loaf': 'keep', 'wheat_bread': 'keep', 'utility_knife': 'keep' }, byType: { 'food': 'keep', 'weapon': 'keep' }, byTag: { 'quest': 'keep', 'healing': 'keep' }, default: 'neutral', desperationThreshold: 15, luxuryThreshold: 60, minimumStockOverrides: { 'bread_loaf': 8, 'wheat_bread': 5 } } }, { id: "citizen_002", name: "Amanen", lastName: "Baker", portrait: "Bin/Contents/UI/Icons/Icon_FemaleCaptured.png", stats: { attributes: { Strength: 1, Dexterity: 2, Intelligence: 2, Willpower: 1, Discipline: 1, Kindness: 2, Happiness: 2, Beauty: 2, Charisma: 2, Health: 10, Stamina: 9, Affection: 0 }, skills: { Academics: 1, Administration: 1, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 2, Acrobatics: 1, Dancing: 2, Performance: 1, Gardening: 1, Survival: 1 }, states: { Fear: 3, Defiance: 4, Stress: 3, Respect: 16, Corruption: 1, Devotion: 9 } }, social: { relationshipStatus: "married", partnerId: "citizen_001", children: [], traits: ["Social", "Generous"], familyId: "family_young_couple" }, inventory: [ { id: 'soft_cheese', itemId: 'soft_cheese', name: 'Soft Cheese', quantity: 4 }, { id: 'fresh_eggs', itemId: 'fresh_eggs', name: 'Fresh Eggs', quantity: 6 }, { id: 'butter_crock', itemId: 'butter_crock', name: 'Butter', quantity: 2 } ], goldPool: 25, foodPool: 38, itemPreferences: { byItem: { 'soft_cheese': 'neutral', 'fresh_eggs': 'neutral', 'butter_crock': 'sell' }, byType: { 'food': 'neutral' }, byTag: { 'quest': 'keep', 'healing': 'neutral', 'luxury': 'sell' }, default: 'sell', desperationThreshold: 12, luxuryThreshold: 50, minimumStockOverrides: { 'soft_cheese': 2, 'fresh_eggs': 3 } } }, { id: "citizen_003", name: "Robert", lastName: "Smith", portrait: "Bin/Contents/UI/Icons/Icon_MaleCaptured.png", stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 2, Willpower: 2, Discipline: 2, Kindness: 1, Happiness: 1, Beauty: 1, Charisma: 2, Health: 10, Stamina: 9, Affection: 0 }, skills: { Academics: 1, Administration: 2, Medicine: 1, Science: 1, "Melee Combat": 2, "Ranged Combat": 2, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 2 }, states: { Fear: 1, Defiance: 8, Stress: 6, Respect: 18, Corruption: 1, Devotion: 12 } }, social: { relationshipStatus: "married", partnerId: "citizen_004", children: ["citizen_005"], traits: ["Diligent", "Brave"], familyId: "family_older_couple" }, inventory: [ { id: 'blacksmith_hammer', itemId: 'blacksmith_hammer', name: "Blacksmith's Hammer" }, { id: 'iron_shovel', itemId: 'iron_shovel', name: 'Iron Shovel' }, { id: 'dried_meat', itemId: 'dried_meat', name: 'Dried Meat', quantity: 3 } ], goldPool: 55, foodPool: 42, quests: [ "quest_robert_patrol" ], itemPreferences: { byItem: { 'blacksmith_hammer': 'keep', 'iron_shovel': 'keep', 'dried_meat': 'neutral' }, byType: { 'tool': 'keep', 'weapon': 'keep', 'food': 'sell' }, byTag: { 'quest': 'keep', 'crafting': 'keep', 'smithing': 'keep' }, default: 'neutral', desperationThreshold: 20, luxuryThreshold: 80, minimumStockOverrides: { 'dried_meat': 2 } } }, { id: "citizen_004", name: "Margaret", lastName: "Smith", portrait: "Bin/Contents/UI/Icons/Icon_FemaleCaptured.png", stats: { attributes: { Strength: 1, Dexterity: 1, Intelligence: 2, Willpower: 2, Discipline: 2, Kindness: 2, Happiness: 2, Beauty: 1, Charisma: 2, Health: 9, Stamina: 8, Affection: 0 }, skills: { Academics: 2, Administration: 1, Medicine: 2, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 2, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 2, Survival: 1 }, states: { Fear: 2, Defiance: 4, Stress: 5, Respect: 17, Corruption: 1, Devotion: 15 } }, social: { relationshipStatus: "married", partnerId: "citizen_003", children: ["citizen_005"], traits: ["Generous", "Pious"], familyId: "family_older_couple" }, inventory: [ { id: 'mortar_pestle', itemId: 'mortar_pestle', name: 'Mortar and Pestle' }, { id: 'herbalist_knife', itemId: 'herbalist_knife', name: "Herbalist's Knife" }, { id: 'field_bandage', itemId: 'field_bandage', name: 'Field Bandage', quantity: 3 } ], goldPool: 48, foodPool: 40, itemPreferences: { byItem: { 'mortar_pestle': 'keep', 'herbalist_knife': 'keep', 'field_bandage': 'keep' }, byType: { 'tool': 'keep', 'food': 'neutral' }, byTag: { 'quest': 'keep', 'healing': 'keep', 'medicine': 'keep', 'herbalism': 'keep' }, default: 'neutral', desperationThreshold: 15, luxuryThreshold: 70, minimumStockOverrides: { 'field_bandage': 2 } } }, { id: "citizen_005", name: "William", lastName: "Smith", portrait: "Bin/Contents/UI/Icons/Icon_MaleCaptured.png", stats: { attributes: { Strength: 1, Dexterity: 1, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 2, Happiness: 2, Beauty: 1, Charisma: 2, Health: 10, Stamina: 10, Affection: 0 }, skills: { Academics: 1, Administration: 0, Medicine: 0, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 1 }, states: { Fear: 4, Defiance: 7, Stress: 2, Respect: 12, Corruption: 0, Devotion: 6 } }, social: { relationshipStatus: "single", partnerId: null, children: [], parents: ["citizen_003", "citizen_004"], traits: ["Social", "Brave"], familyId: "family_older_couple" }, inventory: [ { id: 'wooden_arrow', itemId: 'wooden_arrow', name: 'Wooden Arrow', quantity: 5 }, { id: 'apples', itemId: 'apples', name: 'Apples', quantity: 3 } ], goldPool: 8, foodPool: 18, itemPreferences: { byItem: { 'wooden_arrow': 'keep', 'apples': 'neutral' }, byType: { 'weapon': 'keep', 'food': 'neutral' }, byTag: { 'quest': 'keep', 'ammo': 'keep' }, default: 'neutral', desperationThreshold: 5, luxuryThreshold: 30, minimumStockOverrides: { 'wooden_arrow': 3, 'apples': 1 } } }, { id: "citizen_006", name: "James", lastName: "Miller", portrait: "Bin/Contents/UI/Icons/Icon_MaleCaptured.png", stats: { attributes: { Strength: 2, Dexterity: 2, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 1, Happiness: 2, Beauty: 1, Charisma: 2, Health: 10, Stamina: 10, Affection: 0 }, skills: { Academics: 1, Administration: 1, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 1 }, states: { Fear: 3, Defiance: 6, Stress: 4, Respect: 11, Corruption: 1, Devotion: 5 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Brave", "Optimistic"], familyId: null }, inventory: [ { id: 'utility_knife', itemId: 'utility_knife', name: 'Utility Knife' }, { id: 'rope', itemId: 'rope', name: 'Rope', quantity: 2 }, { id: 'dried_peas', itemId: 'dried_peas', name: 'Dried Peas', quantity: 4 } ], goldPool: 22, foodPool: 24, quests: [ "quest_james_scouting", "task_patrol_roads" ], itemPreferences: { byItem: { 'utility_knife': 'keep', 'rope': 'keep', 'dried_peas': 'sell' }, byType: { 'tool': 'keep', 'weapon': 'keep', 'food': 'sell' }, byTag: { 'quest': 'keep', 'utility': 'keep' }, default: 'sell', desperationThreshold: 10, luxuryThreshold: 50, minimumStockOverrides: { 'rope': 1, 'dried_peas': 2 } } }, { id: "citizen_007", name: "Alice", lastName: "Cooper", portrait: "Bin/Contents/UI/Icons/Icon_FemaleCaptured.png", stats: { attributes: { Strength: 1, Dexterity: 2, Intelligence: 1, Willpower: 1, Discipline: 2, Kindness: 2, Happiness: 2, Beauty: 2, Charisma: 2, Health: 10, Stamina: 9, Affection: 0 }, skills: { Academics: 1, Administration: 1, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 2, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 1 }, states: { Fear: 4, Defiance: 5, Stress: 3, Respect: 13, Corruption: 1, Devotion: 7 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Social", "Diligent"], familyId: null }, inventory: [ { id: 'hard_cheese', itemId: 'hard_cheese', name: 'Hard Cheese', quantity: 2 }, { id: 'ale_mug', itemId: 'ale_mug', name: 'Ale', quantity: 4 }, { id: 'wooden_bucket', itemId: 'wooden_bucket', name: 'Wooden Bucket' } ], goldPool: 19, foodPool: 28, quests: [ "task_collect_firewood", "task_collect_grass" ], itemPreferences: { byItem: { 'hard_cheese': 'neutral', 'ale_mug': 'sell', 'wooden_bucket': 'keep' }, byType: { 'food': 'neutral', 'equipment': 'keep' }, byTag: { 'quest': 'keep', 'utility': 'keep', 'drink': 'sell' }, default: 'neutral', desperationThreshold: 8, luxuryThreshold: 45, minimumStockOverrides: { 'hard_cheese': 1, 'ale_mug': 2 } } }, { id: "citizen_008", name: "Harold", lastName: "Taylor", portrait: "Bin/Contents/UI/Icons/Icon_MaleCaptured.png", stats: { attributes: { Strength: 1, Dexterity: 1, Intelligence: 2, Willpower: 2, Discipline: 2, Kindness: 2, Happiness: 1, Beauty: 1, Charisma: 2, Health: 8, Stamina: 6, Affection: 0 }, skills: { Academics: 2, Administration: 2, Medicine: 2, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 1, Acrobatics: 0, Dancing: 1, Performance: 2, Gardening: 1, Survival: 2 }, states: { Fear: 1, Defiance: 3, Stress: 4, Respect: 19, Corruption: 0, Devotion: 18 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Pious", "Diligent"], familyId: null }, inventory: [ { id: 'wine_bottle', itemId: 'wine_bottle', name: 'Wine', quantity: 1 }, { id: 'honey_pot', itemId: 'honey_pot', name: 'Honey', quantity: 1 }, { id: 'oil_lantern', itemId: 'oil_lantern', name: 'Oil Lantern' } ], goldPool: 65, foodPool: 32, itemPreferences: { byItem: { 'wine_bottle': 'keep', 'honey_pot': 'keep', 'oil_lantern': 'keep' }, byType: { 'food': 'neutral', 'equipment': 'keep' }, byTag: { 'quest': 'keep', 'luxury': 'keep', 'light': 'keep' }, default: 'neutral', desperationThreshold: 25, luxuryThreshold: 90, minimumStockOverrides: { 'wine_bottle': 1, 'honey_pot': 1 } } }, { id: "citizen_009", name: "Giles", lastName: "Fletcher", portrait: "Bin/Contents/UI/Icons/Icon_MaleCaptured.png", stats: { attributes: { Strength: 1, Dexterity: 2, Intelligence: 2, Willpower: 2, Discipline: 1, Kindness: 1, Happiness: 1, Beauty: 1, Charisma: 2, Health: 9, Stamina: 9, Affection: 0 }, skills: { Academics: 2, Administration: 2, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 2, Gardening: 1, Survival: 2 }, states: { Fear: 2, Defiance: 7, Stress: 5, Respect: 14, Corruption: 3, Devotion: 4 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Social", "Worldly"], familyId: null }, inventory: [ { id: 'hand_cart', itemId: 'hand_cart', name: 'Hand Cart' }, { id: 'rope', itemId: 'rope', name: 'Rope', quantity: 3 }, { id: 'salted_fish', itemId: 'salted_fish', name: 'Salted Fish', quantity: 4 }, { id: 'wine_bottle', itemId: 'wine_bottle', name: 'Wine', quantity: 2 } ], goldPool: 95, foodPool: 30, quests: [ "task_deliver_supplies" ], itemPreferences: { byItem: { 'hand_cart': 'keep', 'rope': 'neutral', 'salted_fish': 'sell', 'wine_bottle': 'sell' }, byType: { 'food': 'sell', 'equipment': 'keep' }, byTag: { 'quest': 'keep', 'transport': 'keep', 'luxury': 'sell' }, default: 'sell', desperationThreshold: 30, luxuryThreshold: 100, minimumStockOverrides: { 'rope': 1, 'salted_fish': 2 } } }, { id: "citizen_010", name: "Isabel", lastName: "Wright", portrait: "Bin/Contents/UI/Icons/Icon_FemaleCaptured.png", stats: { attributes: { Strength: 1, Dexterity: 2, Intelligence: 2, Willpower: 2, Discipline: 2, Kindness: 1, Happiness: 1, Beauty: 2, Charisma: 1, Health: 9, Stamina: 8, Affection: 0 }, skills: { Academics: 1, Administration: 1, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 1 }, states: { Fear: 6, Defiance: 8, Stress: 9, Respect: 10, Corruption: 2, Devotion: 3 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Brave", "Pessimistic"], familyId: null }, inventory: [ { id: 'turnips', itemId: 'turnips', name: 'Turnips', quantity: 6 }, { id: 'onions', itemId: 'onions', name: 'Onions', quantity: 4 }, { id: 'utility_knife', itemId: 'utility_knife', name: 'Utility Knife' } ], goldPool: 12, foodPool: 15, quests: [ "task_hunt_game" ], itemPreferences: { byItem: { 'turnips': 'keep', 'onions': 'keep', 'utility_knife': 'keep' }, byType: { 'food': 'keep', 'weapon': 'keep', 'tool': 'keep' }, byTag: { 'quest': 'keep', 'vegetable': 'keep' }, default: 'keep', desperationThreshold: 5, luxuryThreshold: 40, minimumStockOverrides: { 'turnips': 4, 'onions': 3 } } }, { id: "citizen_011", name: "Marcus", lastName: "Ironwood", portrait: "Bin/Contents/UI/Icons/Icon_MaleCaptured.png", stats: { attributes: { Strength: 2, Dexterity: 2, Intelligence: 1, Willpower: 2, Discipline: 2, Kindness: 1, Happiness: 1, Beauty: 1, Charisma: 1, Health: 10, Stamina: 9, Affection: 0 }, skills: { Academics: 1, Administration: 1, Medicine: 1, Science: 1, "Melee Combat": 1, "Ranged Combat": 1, Domestic: 2, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 1, Survival: 2 }, states: { Fear: 2, Defiance: 7, Stress: 5, Respect: 15, Corruption: 1, Devotion: 8 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Hardworking", "Stubborn"], familyId: null }, inventory: [ { id: 'carpenters_hammer', itemId: 'carpenters_hammer', name: "Carpenter's Hammer" }, { id: 'hand_saw', itemId: 'hand_saw', name: 'Hand Saw' }, { id: 'wood_chisel', itemId: 'wood_chisel', name: 'Wood Chisel' }, { id: 'timber_bundle', itemId: 'timber_bundle', name: 'Timber Bundle', quantity: 3 } ], goldPool: 70, foodPool: 26, quests: [ "task_collect_rare_wood" ], itemPreferences: { byItem: { 'carpenters_hammer': 'keep', 'hand_saw': 'keep', 'wood_chisel': 'keep', 'timber_bundle': 'sell' }, byType: { 'tool': 'keep', 'resource': 'sell' }, byTag: { 'quest': 'keep', 'carpentry': 'keep', 'woodworking': 'keep', 'crafting': 'keep' }, default: 'neutral', desperationThreshold: 25, luxuryThreshold: 85, minimumStockOverrides: { 'carpenters_hammer': 1, 'hand_saw': 1, 'wood_chisel': 1 } } }, { id: "citizen_012", name: "Lydia", lastName: "Moonwhisper", portrait: "Bin/Contents/UI/Icons/Icon_FemaleCaptured.png", stats: { attributes: { Strength: 1, Dexterity: 2, Intelligence: 2, Willpower: 2, Discipline: 1, Kindness: 2, Happiness: 2, Beauty: 1, Charisma: 2, Health: 9, Stamina: 8, Affection: 0 }, skills: { Academics: 2, Administration: 1, Medicine: 2, Science: 2, "Melee Combat": 0, "Ranged Combat": 1, Domestic: 1, Acrobatics: 1, Dancing: 1, Performance: 1, Gardening: 2, Survival: 1 }, states: { Fear: 3, Defiance: 4, Stress: 4, Respect: 16, Corruption: 1, Devotion: 12 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Wise", "Mysterious"], familyId: null }, inventory: [ { id: 'mortar_pestle', itemId: 'mortar_pestle', name: 'Mortar and Pestle' }, { id: 'herbalist_knife', itemId: 'herbalist_knife', name: "Herbalist's Knife" }, { id: 'drying_rack', itemId: 'drying_rack', name: 'Herb Drying Rack' }, { id: 'medicine_pouch', itemId: 'medicine_pouch', name: 'Medicine Pouch' }, { id: 'healing_draught', itemId: 'healing_draught', name: 'Healing Draught', quantity: 4 } ], goldPool: 58, foodPool: 34, itemPreferences: { byItem: { 'mortar_pestle': 'keep', 'herbalist_knife': 'keep', 'drying_rack': 'keep', 'medicine_pouch': 'keep', 'healing_draught': 'sell' }, byType: { 'tool': 'keep', 'equipment': 'keep', 'consumable': 'sell' }, byTag: { 'quest': 'keep', 'herbalism': 'keep', 'medicine': 'keep', 'healing': 'sell' }, default: 'neutral', desperationThreshold: 20, luxuryThreshold: 75, minimumStockOverrides: { 'healing_draught': 2 } } } ]>>
<<set $eventDatabase = [ { id: "abandoned", type: "critical", priority: 10, severity: "high", message: "Your settlement is completely abandoned. Nothing remains but empty buildings and memories.", image: "Bin/Contents/UI/Event_Images/Building_Collapse_A_Structure_Falls.png", conditions: [ { type: "all_zero", stats: ["happiness", "security", "citizens", "loyalty", "crime", "slaves"] } ] }, { id: "collapse", type: "critical", priority: 8, severity: "high", message: "Your settlement is on the verge of total collapse! All critical systems are failing.", image: "Bin/Contents/UI/Event_Images/Building_Collapse_A_Structure_Falls.png", conditions: [ { type: "stat", name: "happiness", operator: "<", value: 30 }, { type: "stat", name: "loyalty", operator: "<", value: 30 }, { type: "stat", name: "security", operator: "<", value: 30 } ] }, { id: "lack_food", type: "food", priority: 6, severity: "high", message: "My Liege, stores run thin and families grow restless. Children cry, and whispers of discontent spread ever more quickly.", effects: "-Happiness, -Loyalty, +Crime", image: "Bin/Contents/UI/Event_Images/Lack_of_Food_Hunger_Looms.png", conditions: [ { type: "resource", name: "food", operator: "<", value: 50 } ] }, { id: "crime_wave", type: "crisis", priority: 4, severity: "high", message: "Criminal gangs are taking control of parts of your city.", image: "Bin/Contents/UI/Event_Images/Bandit_Raid_Thieves_came_in_the_Night.png", conditions: [ { type: "stat", name: "crime", operator: ">", value: 70 } ] }, { id: "tax_strike", type: "crisis", priority: 5, severity: "high", message: "Workers abandon their posts in protest of ruinous taxation. Production grinds to a crawl until morale recovers.", image: "Bin/Contents/UI/Event_Images/Riot_Chaos_in_the_Streets.png", conditions: [ { type: "stat", name: "taxUnrest", operator: ">=", value: 2 }, { type: "stat", name: "happiness", operator: "<", value: 45 } ] }, { id: "riots", type: "crisis", priority: 3, severity: "medium", message: "Citizens are rioting in the streets! Happiness is dangerously low.", image: "Bin/Contents/UI/Event_Images/Riot_Chaos_in_the_Streets.png", conditions: [ { type: "stat", name: "happiness", operator: "<", value: 30 } ] }, { id: "tax_exodus", type: "critical", priority: 6, severity: "high", message: "Entire families flee your realm to escape the 200% tithe. Empty homes echo across the streets.", image: "Bin/Contents/UI/Event_Images/Building_Collapse_A_Structure_Falls.png", conditions: [ { type: "stat", name: "taxUnrest", operator: ">=", value: 3 }, { type: "stat", name: "loyalty", operator: "<", value: 35 } ] }, { id: "plenty_food", type: "positive", priority: 1, severity: "low", message: "My Liege, the fields yield beyond measure. Granaries overflow, and the people rejoice at their good fortune and abundance.", effects: "+Happiness, +Loyalty", image: "Bin/Contents/UI/Event_Images/Plenty_of_Food_A_Bountiful_Harvest.png", conditions: [ { type: "resource", name: "food", operator: ">", value: 500 } ] }, { id: "hunters_bounty", type: "positive", priority: 1, severity: "low", message: "My Liege, the hunters return laden with game. Fires burn bright, and even the poorest eat well tonight.", effects: "+Happiness, +Loyalty", image: "Bin/Contents/UI/Event_Images/Hunters_Bounty_The_Forest_Provides.png", conditions: [ { type: "resource", name: "food", operator: ">", value: 300 }, { type: "resource", name: "food", operator: "<=", value: 500 } ] }, { id: "prosperity", type: "positive", priority: 2, severity: "low", message: "My Liege, trade thrives, and the people prosper greatly. Laughter fills the streets under your wise and steady rule.", effects: "+Happiness, +Loyalty, -Crime", image: "Bin/Contents/UI/Event_Images/Prosperity_Golden_Times.png", conditions: [ { type: "stat", name: "happiness", operator: ">", value: 80 }, { type: "stat", name: "loyalty", operator: ">", value: 80 }, { type: "resource", name: "gold", operator: ">", value: 1000 } ] } ]>> <<set $peacefulMessages = [ "My Liege, today passes without event or alarm. The people toil, rest, and dream peacefully under calm skies.", "My Liege, no news nor trouble stirs at all. The realm breathes in deep calm and ease.", "My Liege, the sun rises gently. Birds sing, farmers stir, and the day begins in quiet order.", "My Liege, as dusk falls, hearths glow warmly. Families gather, sharing bread and laughter beneath tranquil skies.", "My Liege, routine work continues. The day moves on as workers labor steadily at fields and forges.", "My Liege, a calm morning greets the settlement. Dawn arrives without disturbance as the realm awakens.", "My Liege, the evening brings peaceful rest. Hearths burn bright as families gather in quiet contentment." ]>> <<set $peacefulImages = [ "Bin/Contents/UI/Event_Images/Nothing_Happens_A_Quiet_Day.png", "Bin/Contents/UI/Event_Images/Calm Morning_Dawn_Without_Disturbance.png", "Bin/Contents/UI/Event_Images/Routine_Work_The_Day_Moves_On.png", "Bin/Contents/UI/Event_Images/Normal Rain_Nourishment_from_the_Skies.png" ]>>
<div class="right_container"> <<include "gui-button">> </div> <div class="left_container"> <div class="basic_information"> <div class="character_icon" onclick="openUIScreen('player')"> <img src="Bin/Contents/Characters/Human_Storyline/Story/Player/Frame.png" alt="human_avatar"> </div> <<include "characterstatusgui">> </div> <div class="left_container_buttons"> <button class="left_container_button" data-gui-button="settings" data-tooltip="Settings" data-tooltip-placement="top" onclick="openSettings()"> <img src="Bin/Contents/UI/Buttons/sbtn_settings.png" alt="settings"> </button> <button class="left_container_button" data-gui-button="save" data-tooltip="Save" data-tooltip-placement="top" onclick="saveGame()"> <img src="Bin/Contents/UI/Buttons/sbtn_save.png" alt="save"> </button> <button class="left_container_button" data-gui-button="skip-time" data-tooltip="Skip 30 minutes" data-tooltip-placement="top" onclick="window.useSkipTime(); return false;"> <img src="Bin/Contents/UI/Buttons/sbtn_skiptime.png" alt="time_skip"> </button> <div class="time_and_money"> <div class="dynamic_time_money"> <span class="time_value">12:00 AM | Monday</span> </div> </div> <div class="return_home"> <button class="left_container_button" data-gui-button="return-home" data-tooltip-id="return-home" data-tooltip-placement="top" onclick="if (window.CompassNavigation && typeof window.CompassNavigation.returnToBlackmoorCastle === 'function') { window.CompassNavigation.returnToBlackmoorCastle(); } event.stopPropagation(); return false;"> <img src="Bin/Contents/UI/Buttons/sbtn_rhome.png" alt="return_home"> </button> </div> </div> </div>
<div class="right_container"> <!-- Top row --> <div class="top_row"> <button class="icon_button" onclick="openUIScreen('settlement_overview')"> <span class="tooltip">Castle Management</span> <img src="Bin/Contents/UI/Buttons/btn_city.png" alt="Castle"> </button> <button class="icon_button" onclick="openUIScreen('player')"> <img src="Bin/Contents/UI/Buttons/btn_companion.png" alt="Companion"> </button> <button class="icon_button" onclick="openUIScreen('army')"> <img src="Bin/Contents/UI/Buttons/btn_army.png" alt="Army"> </button> <button class="icon_button"> <img src="Bin/Contents/UI/Buttons/btn_quest.png" alt="Quest"> </button> </div> <!-- Bottom row --> <div class="bottom_row"> <button class="icon_button" onclick="openWorldMap()"> <img src="Bin/Contents/UI/Buttons/btn_map.png" alt="Map"> </button> <button class="icon_button" onclick="openUIScreen('game_prison')"> <img src="Bin/Contents/UI/Buttons/btn_dungeon.png" alt="Prisoner"> </button> <button class="icon_button" onclick="openUIScreen('player_inventory')"> <img src="Bin/Contents/UI/Buttons/btn_inventory.png" alt="Inventory"> </button> <button class="icon_button"> <img src="Bin/Contents/UI/Buttons/btn_message.png" alt="Message"> </button> </div> </div> <div class="left_container"> <div class="basic_information"> <div class="character_icon"> <img src="Bin\Contents\Characters\Human_Storyline\Story\Player\Frame.png" alt="human_avatar"> </div> <div class="character_status"> <div class="status_panel"> <div class="status_header">Current State</div> <div class="status_info"> <div class="status_row"> <span class="status_bullet">•</span> <span class="status_label">Normal Health</span> <span class="status_bullet">•</span> <span class="status_label">Dirty</span> </div> <div class="status_row"> <span class="status_bullet">•</span> <span class="status_label">Hungry</span> <span class="status_bullet">•</span> <span class="status_label">Satisfied</span> </div> <div class="resources_row"> <div class="resource_item"> <img src="Bin/Contents/UI/Icons/Icon_ResourceGold.png" alt="gold"> <span class="resource_value">0</span> </div> <div class="resource_item"> <img src="Bin/Contents/UI/Icons/Icon_ResourceInfluence.png" alt="cup"> <span class="resource_value">0</span> </div> </div> </div> </div> </div> </div> <div class="left_container_buttons"> <button class="setting_button" onclick="openSettings()"> <img src="Bin/Contents/UI/Buttons/sbtn_settings.png" alt="settings"> </button> <button class="save_button" onclick="saveGame()"> <img src="Bin/Contents/UI/Buttons/sbtn_save.png" alt="save"> </button> <div class="return_home"> <button class="left_container_button"> <img src="Bin/Contents/UI/Buttons/sbtn_rhome.png" alt="return_home"> </button> </div> <div class="time_and_money"> <div class="dynamic_time_money"> <span class="time_value">12:00 AM | Monday</span> </div> </div> </div> </div>
<style> /* ===== Credits Screen ===== */ .credits-screen { width: 1207px; height: 650px; background-image: url('Bin/Contents/UI/Resources/Credits_Background.png'); background-repeat: no-repeat; background-size: 100% 100%; position: fixed; left: 50%; top: 50%; transform: translate(-50%, -50%); margin: 0; z-index: 1; overflow: hidden; font-family: 'MedievalSharpBook', serif; } .credits-screen br { display: none !important; } /* Emblem + Stamp */ .credits-emblem { position: absolute; top: 18px; left: 50%; transform: translateX(-50%); width: 220px; z-index: 2; pointer-events: none; } .credits-stamp { position: absolute; right: 40px; bottom: 20px; width: 94px; height: 94px; z-index: 2; pointer-events: none; } .credits-content { position: absolute; left: 50%; top: 190px; transform: translateX(-50%); width: 1000px; display: flex; justify-content: space-between; align-items: flex-start; gap: 30px; z-index: 3; } /* ===== Bemorian Studios ===== */ .bemorian-container { width: 300px; text-align: center; position: relative; top: -90px; color: #791919; text-shadow: 0 4px 4px rgba(0, 0, 0, 0.25); } .bemorian-icon { width: 50px; height: 50px; margin: 0 auto 0 auto; display: block; } .bemorian-title { font-size: 18px; text-decoration: underline; margin-bottom: 10px; margin-top: 5px; } .bemorian-name { font-size: 16px; margin: 6px 0; line-height: 1.1; } .bemorian-role { display: block; font-size: 12px; opacity: 0.9; margin-top: 2px; } /* ===== Sponsors ===== */ .sponsors-container { width: 500px; text-align: center; color: #1a1a1a; text-shadow: 0 4px 4px rgba(0, 0, 0, 0.25); } .sponsors-names { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px 18px; max-width: 100%; } .sponsor-name { margin: 0; white-space: nowrap; max-width: 200px; overflow: hidden; text-overflow: ellipsis; text-align: center; font-size: 16px; line-height: 1.1; text-shadow: 0 2px 2px rgba(0, 0, 0, 0.25); background: none; } .sponsors-icon { width: 45px; height: 45px; margin: 0 auto 0 auto; display: block; } .sponsors-title { font-size: 18px; text-decoration: underline; margin-bottom: 10px; margin-top: 5px; } .sponsors-text { font-size: 16px; margin: 0; line-height: 1.2; position: relative; width: 430px; top: 220px; } .sponsors-text small { display: block; font-size: 12px; margin-top: 6px; } /* ===== Community ===== */ .community-container { width: 300px; text-align: center; position: relative; top: -90px; color: #002C49; text-shadow: 0 4px 4px rgba(0, 0, 0, 0.25); } .community-icon { width: 50px; height: 50px; margin: 0 auto 0 auto; display: block; } .community-title { font-size: 18px; text-decoration: underline; margin-bottom: 10px; margin-top: 5px; } .community-name { font-size: 16px; margin: 6px 0; line-height: 1.1; } .community-role { display: block; font-size: 12px; opacity: 0.9; margin-top: 2px; } /* ===== Credits Return Button ===== */ .credits-back { position: absolute; left: 50%; bottom: 32px; transform: translateX(-50%); z-index: 4; } button.credits-return-btn { width: 150px; height: 38px; background-image: url('Bin/Contents/UI/Buttons/btn_settingsutils.png'); background-size: 100% 100%; background-repeat: no-repeat; background-color: transparent; border: none; padding: 0; cursor: pointer; position: relative; top: -4px; display: flex; align-items: center; justify-content: center; color: #FFDDAD; transition: filter 0.25s ease, box-shadow 0.25s ease, transform 0.25s ease; outline: none !important; box-shadow: none; appearance: none; -webkit-appearance: none; -webkit-tap-highlight-color: transparent; } button.credits-return-btn:hover { filter: brightness(1.4) contrast(1.15); box-shadow: 0 0 15px rgba(255, 221, 173, 0.9), 0 0 25px rgba(255, 180, 0, 0.5); } button.credits-return-btn:active { transform: scale(0.95); } button.credits-return-btn:focus, button.credits-return-btn:focus-visible, button.credits-return-btn:active { outline: none !important; box-shadow: none !important; } button.credits-return-btn::-moz-focus-inner { border: 0; } .credits-return-text { font-family: 'MedievalSharpBook', serif; font-size: 16px; color: #FFDDAD; text-transform: uppercase; pointer-events: none; position: relative; top: 1px; } </style> <div class="fullscreenbackground"> <div class="credits-screen"> <img class="credits-emblem" src="Bin/Contents/UI/Resources/Credits_Emblem.png" alt="Emblem"> <img class="credits-stamp" src="Bin/Contents/UI/Icons/Credits_Icon_Stamp.png" alt=""> <div class="credits-content"> <!-- Bemorian Studios --> <section class="bemorian-container"> <img class="bemorian-icon" src="Bin/Contents/UI/Icons/Credits_Icon_StudioTeam.png" alt=""> <h3 class="bemorian-title">BEMORIAN STUDIOS TEAM</h3> <p class="bemorian-name">Bishopp<span class="bemorian-role">(Artist, Composer, Writer)</span></p> <p class="bemorian-name">Pawn<span class="bemorian-role">(Developer)</span></p> <p class="bemorian-name">TheUnholyOne<span class="bemorian-role">(Community Manager)</span></p> </section> <!-- Sponsors --> <section class="sponsors-container"> <img class="sponsors-icon" src="Bin/Contents/UI/Icons/Credits_Icon_Supporters.png" alt=""> <h3 class="bemorian-title sponsors-title">PATRONS OF THE REALM</h3> <div class="sponsors-names"> <p class="sponsor-name">Roslita</p> </div> <p class="sponsors-text"> Dear Patreon & SubscribeStar Supporters <small>(If you would like your name to be listed here, send us a message on Discord)</small> </p> </section> <!-- Community --> <section class="community-container"> <img class="community-icon" src="Bin/Contents/UI/Icons/Credits_Icon_Community.png" alt=""> <h3 class="community-title">COMMUNITY CONTRIBUTORS</h3> <p class="community-name">Frederik van Altenhove<span class="community-role">(Alpha Tester)</span></p> <p class="community-name">TheUnholyOne<span class="community-role">(Alpha Tester)</span></p> <p class="community-name">MrChill<span class="community-role">(Alpha Tester)</span></p> <p class="community-name">_Zed15<span class="community-role">(Alpha Tester)</span></p> <p class="community-name">Roslita<span class="community-role">(Alpha Tester)</span></p> <p class="community-name">Akira<span class="community-role">(Map Contribution)</span></p> </section> </div> <div class="credits-back"> <button class="credits-return-btn" onclick="goToPassage('settings')"> <span class="credits-return-text">Return</span> </button> </div> </div> </div>
<<set $immigrantDatabase = [ { id: "citizen_101", entryType: "immigration", immigrationGroupId: "family_ashdown", immigrationKey: "ashdown_robert", immigrationTags: ["family_medium", "laborer"], name: "Robert", lastName: "Ashdown", gender: "male", age: 34, profession: "woodcutter", portrait: "portraits/robert_ashdown.png", title: "Caravan Laborer", stats: { attributes: { Strength: 16, Dexterity: 13, Intelligence: 12, Willpower: 14, Discipline: 15, Kindness: 14, Happiness: 12, Beauty: 11, Charisma: 12, Health: 92, Stamina: 88, Affection: 0 }, skills: { Academics: 6, Administration: 5, Medicine: 4, Science: 5, "Melee Combat": 12, "Ranged Combat": 9, Domestic: 9, Acrobatics: 8, Dancing: 6, Performance: 7, Gardening: 12, Survival: 13 }, states: { Fear: 2, Defiance: 5, Stress: 6, Respect: 13, Corruption: 0, Devotion: 7 } }, social: { relationshipStatus: "married", partnerId: null, children: [], traits: ["Diligent", "Brave"], familyId: null } }, { id: "citizen_102", entryType: "immigration", immigrationGroupId: "family_ashdown", immigrationKey: "ashdown_matilda", immigrationPartnerKey: "ashdown_robert", immigrationTags: ["family_medium", "laborer"], name: "Matilda", lastName: "Ashdown", gender: "female", age: 32, profession: "weaver", portrait: "portraits/matilda_ashdown.png", title: "Caravan Weaver", stats: { attributes: { Strength: 12, Dexterity: 15, Intelligence: 14, Willpower: 13, Discipline: 15, Kindness: 17, Happiness: 14, Beauty: 13, Charisma: 15, Health: 90, Stamina: 82, Affection: 0 }, skills: { Academics: 8, Administration: 7, Medicine: 6, Science: 7, "Melee Combat": 5, "Ranged Combat": 6, Domestic: 17, Acrobatics: 9, Dancing: 10, Performance: 11, Gardening: 10, Survival: 9 }, states: { Fear: 3, Defiance: 4, Stress: 5, Respect: 12, Corruption: 0, Devotion: 8 } }, social: { relationshipStatus: "married", partnerId: null, children: [], traits: ["Generous", "Optimistic"], familyId: null } }, { id: "citizen_103", entryType: "immigration", immigrationGroupId: "family_ashdown", immigrationKey: "ashdown_edwin", immigrationParentKeys: ["ashdown_robert", "ashdown_matilda"], immigrationTags: ["family_medium", "youth"], name: "Edwin", lastName: "Ashdown", gender: "male", age: 11, profession: "student", portrait: "portraits/edwin_ashdown.png", title: "Caravan Youth", stats: { attributes: { Strength: 9, Dexterity: 13, Intelligence: 12, Willpower: 11, Discipline: 10, Kindness: 15, Happiness: 17, Beauty: 11, Charisma: 13, Health: 96, Stamina: 92, Affection: 0 }, skills: { Academics: 9, Administration: 5, Medicine: 4, Science: 6, "Melee Combat": 7, "Ranged Combat": 8, Domestic: 7, Acrobatics: 12, Dancing: 9, Performance: 8, Gardening: 8, Survival: 7 }, states: { Fear: 4, Defiance: 6, Stress: 3, Respect: 10, Corruption: 0, Devotion: 4 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Curious", "Brave"], familyId: null } }, { id: "citizen_104", entryType: "immigration", immigrationGroupId: "family_ashdown", immigrationKey: "ashdown_emma", immigrationParentKeys: ["ashdown_robert", "ashdown_matilda"], immigrationTags: ["family_medium", "youth"], name: "Emma", lastName: "Ashdown", gender: "female", age: 7, profession: "student", portrait: "portraits/emma_ashdown.png", title: "Caravan Child", stats: { attributes: { Strength: 7, Dexterity: 12, Intelligence: 11, Willpower: 10, Discipline: 9, Kindness: 16, Happiness: 18, Beauty: 12, Charisma: 14, Health: 98, Stamina: 95, Affection: 0 }, skills: { Academics: 8, Administration: 4, Medicine: 4, Science: 5, "Melee Combat": 4, "Ranged Combat": 5, Domestic: 8, Acrobatics: 13, Dancing: 11, Performance: 10, Gardening: 6, Survival: 5 }, states: { Fear: 3, Defiance: 5, Stress: 2, Respect: 9, Corruption: 0, Devotion: 4 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Cheerful", "Imaginative"], familyId: null } }, { id: "citizen_105", entryType: "immigration", immigrationGroupId: "family_valen", immigrationKey: "valen_gareth", immigrationTags: ["family_large", "laborer"], name: "Gareth", lastName: "Valen", gender: "male", age: 42, profession: "farmer", portrait: "portraits/gareth_valen.png", title: "Refugee Farmer", stats: { attributes: { Strength: 15, Dexterity: 12, Intelligence: 13, Willpower: 15, Discipline: 14, Kindness: 16, Happiness: 10, Beauty: 10, Charisma: 12, Health: 88, Stamina: 86, Affection: 0 }, skills: { Academics: 7, Administration: 6, Medicine: 5, Science: 7, "Melee Combat": 10, "Ranged Combat": 8, Domestic: 11, Acrobatics: 7, Dancing: 6, Performance: 7, Gardening: 16, Survival: 14 }, states: { Fear: 4, Defiance: 6, Stress: 7, Respect: 12, Corruption: 0, Devotion: 6 } }, social: { relationshipStatus: "married", partnerId: null, children: [], traits: ["Hardworking", "Steadfast"], familyId: null } }, { id: "citizen_106", entryType: "immigration", immigrationGroupId: "family_valen", immigrationKey: "valen_elsa", immigrationPartnerKey: "valen_gareth", immigrationTags: ["family_large", "healer"], name: "Elsa", lastName: "Valen", gender: "female", age: 40, profession: "herbalist", portrait: "portraits/elsa_valen.png", title: "Refugee Herbalist", stats: { attributes: { Strength: 11, Dexterity: 14, Intelligence: 16, Willpower: 15, Discipline: 15, Kindness: 18, Happiness: 12, Beauty: 12, Charisma: 14, Health: 85, Stamina: 80, Affection: 0 }, skills: { Academics: 12, Administration: 8, Medicine: 17, Science: 13, "Melee Combat": 6, "Ranged Combat": 7, Domestic: 15, Acrobatics: 7, Dancing: 9, Performance: 10, Gardening: 15, Survival: 13 }, states: { Fear: 3, Defiance: 5, Stress: 6, Respect: 14, Corruption: 0, Devotion: 9 } }, social: { relationshipStatus: "married", partnerId: null, children: [], traits: ["Compassionate", "Wise"], familyId: null } }, { id: "citizen_107", entryType: "immigration", immigrationGroupId: "family_valen", immigrationKey: "valen_marta", immigrationTags: ["family_large", "elder"], name: "Marta", lastName: "Valen", gender: "female", age: 68, profession: "storyteller", portrait: "portraits/marta_valen.png", title: "Refugee Elder", stats: { attributes: { Strength: 7, Dexterity: 9, Intelligence: 17, Willpower: 18, Discipline: 15, Kindness: 19, Happiness: 11, Beauty: 9, Charisma: 16, Health: 70, Stamina: 58, Affection: 0 }, skills: { Academics: 15, Administration: 10, Medicine: 9, Science: 11, "Melee Combat": 4, "Ranged Combat": 5, Domestic: 12, Acrobatics: 3, Dancing: 5, Performance: 16, Gardening: 10, Survival: 12 }, states: { Fear: 2, Defiance: 3, Stress: 5, Respect: 18, Corruption: 0, Devotion: 11 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Pious", "Perceptive"], familyId: null } }, { id: "citizen_108", entryType: "immigration", immigrationGroupId: "family_valen", immigrationKey: "valen_oliver", immigrationParentKeys: ["valen_gareth", "valen_elsa"], immigrationTags: ["family_large", "youth"], name: "Oliver", lastName: "Valen", gender: "male", age: 16, profession: "apprentice", portrait: "portraits/oliver_valen.png", title: "Refugee Apprentice", stats: { attributes: { Strength: 12, Dexterity: 14, Intelligence: 13, Willpower: 12, Discipline: 12, Kindness: 15, Happiness: 13, Beauty: 12, Charisma: 13, Health: 92, Stamina: 88, Affection: 0 }, skills: { Academics: 9, Administration: 6, Medicine: 5, Science: 8, "Melee Combat": 9, "Ranged Combat": 8, Domestic: 8, Acrobatics: 11, Dancing: 8, Performance: 8, Gardening: 11, Survival: 10 }, states: { Fear: 4, Defiance: 7, Stress: 5, Respect: 11, Corruption: 0, Devotion: 5 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Ambitious", "Loyal"], familyId: null } }, { id: "citizen_109", entryType: "immigration", immigrationGroupId: "family_valen", immigrationKey: "valen_sara", immigrationParentKeys: ["valen_gareth", "valen_elsa"], immigrationTags: ["family_large", "youth"], name: "Sara", lastName: "Valen", gender: "female", age: 9, profession: "student", portrait: "portraits/sara_valen.png", title: "Refugee Child", stats: { attributes: { Strength: 8, Dexterity: 13, Intelligence: 12, Willpower: 11, Discipline: 10, Kindness: 17, Happiness: 15, Beauty: 12, Charisma: 14, Health: 95, Stamina: 93, Affection: 0 }, skills: { Academics: 8, Administration: 4, Medicine: 5, Science: 6, "Melee Combat": 4, "Ranged Combat": 5, Domestic: 9, Acrobatics: 12, Dancing: 11, Performance: 10, Gardening: 7, Survival: 6 }, states: { Fear: 3, Defiance: 5, Stress: 3, Respect: 9, Corruption: 0, Devotion: 5 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Kind", "Observant"], familyId: null } }, { id: "citizen_110", entryType: "immigration", immigrationGroupId: "family_greenfield", immigrationKey: "greenfield_julia", immigrationTags: ["family_small", "artisan"], name: "Julia", lastName: "Greenfield", gender: "female", age: 27, profession: "potter", portrait: "portraits/julia_greenfield.png", title: "Traveling Potter", stats: { attributes: { Strength: 11, Dexterity: 17, Intelligence: 14, Willpower: 13, Discipline: 14, Kindness: 15, Happiness: 15, Beauty: 15, Charisma: 16, Health: 94, Stamina: 86, Affection: 0 }, skills: { Academics: 10, Administration: 8, Medicine: 6, Science: 9, "Melee Combat": 6, "Ranged Combat": 7, Domestic: 16, Acrobatics: 10, Dancing: 12, Performance: 11, Gardening: 9, Survival: 8 }, states: { Fear: 3, Defiance: 4, Stress: 4, Respect: 12, Corruption: 0, Devotion: 6 } }, social: { relationshipStatus: "married", partnerId: null, children: [], traits: ["Creative", "Patient"], familyId: null } }, { id: "citizen_111", entryType: "immigration", immigrationGroupId: "family_greenfield", immigrationKey: "greenfield_isaac", immigrationPartnerKey: "greenfield_julia", immigrationTags: ["family_small", "artisan"], name: "Isaac", lastName: "Greenfield", gender: "male", age: 29, profession: "glassblower", portrait: "portraits/isaac_greenfield.png", title: "Traveling Artisan", stats: { attributes: { Strength: 12, Dexterity: 18, Intelligence: 15, Willpower: 12, Discipline: 13, Kindness: 13, Happiness: 14, Beauty: 13, Charisma: 14, Health: 93, Stamina: 88, Affection: 0 }, skills: { Academics: 9, Administration: 7, Medicine: 5, Science: 10, "Melee Combat": 7, "Ranged Combat": 8, Domestic: 12, Acrobatics: 11, Dancing: 9, Performance: 10, Gardening: 8, Survival: 7 }, states: { Fear: 3, Defiance: 5, Stress: 4, Respect: 11, Corruption: 0, Devotion: 6 } }, social: { relationshipStatus: "married", partnerId: null, children: [], traits: ["Meticulous", "Sociable"], familyId: null } }, { id: "citizen_112", entryType: "immigration", immigrationGroupId: "family_greenfield", immigrationKey: "greenfield_lyra", immigrationParentKeys: ["greenfield_julia", "greenfield_isaac"], immigrationTags: ["family_small", "youth"], name: "Lyra", lastName: "Greenfield", gender: "female", age: 4, profession: "student", portrait: "portraits/lyra_greenfield.png", title: "Traveling Toddler", stats: { attributes: { Strength: 5, Dexterity: 9, Intelligence: 9, Willpower: 8, Discipline: 7, Kindness: 16, Happiness: 19, Beauty: 12, Charisma: 13, Health: 99, Stamina: 98, Affection: 0 }, skills: { Academics: 4, Administration: 2, Medicine: 2, Science: 3, "Melee Combat": 2, "Ranged Combat": 2, Domestic: 4, Acrobatics: 9, Dancing: 9, Performance: 8, Gardening: 3, Survival: 2 }, states: { Fear: 2, Defiance: 3, Stress: 1, Respect: 7, Corruption: 0, Devotion: 3 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Playful", "Kind"], familyId: null } }, { id: "citizen_113", entryType: "immigration", immigrationKey: "artisan_silas", immigrationTags: ["artisan"], name: "Silas", lastName: "Greyhammer", gender: "male", age: 46, profession: "blacksmith", portrait: "portraits/silas_greyhammer.png", title: "Journeyman Smith", stats: { attributes: { Strength: 18, Dexterity: 14, Intelligence: 13, Willpower: 15, Discipline: 16, Kindness: 11, Happiness: 13, Beauty: 10, Charisma: 11, Health: 90, Stamina: 92, Affection: 0 }, skills: { Academics: 9, Administration: 8, Medicine: 5, Science: 10, "Melee Combat": 14, "Ranged Combat": 9, Domestic: 10, Acrobatics: 7, Dancing: 6, Performance: 8, Gardening: 7, Survival: 9 }, states: { Fear: 2, Defiance: 5, Stress: 5, Respect: 14, Corruption: 0, Devotion: 5 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Stoic", "Skilled"], familyId: null } }, { id: "citizen_114", entryType: "immigration", immigrationKey: "artisan_elise", immigrationTags: ["artisan"], name: "Elise", lastName: "Hartwell", gender: "female", age: 33, profession: "tailor", portrait: "portraits/elise_hartwell.png", title: "Master Tailor", stats: { attributes: { Strength: 10, Dexterity: 18, Intelligence: 15, Willpower: 12, Discipline: 15, Kindness: 14, Happiness: 16, Beauty: 15, Charisma: 16, Health: 91, Stamina: 85, Affection: 0 }, skills: { Academics: 11, Administration: 9, Medicine: 6, Science: 8, "Melee Combat": 5, "Ranged Combat": 6, Domestic: 18, Acrobatics: 10, Dancing: 13, Performance: 12, Gardening: 8, Survival: 7 }, states: { Fear: 3, Defiance: 4, Stress: 4, Respect: 13, Corruption: 0, Devotion: 5 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Creative", "Meticulous"], familyId: null } }, { id: "citizen_115", entryType: "immigration", immigrationKey: "healer_amon", immigrationTags: ["healer"], name: "Amon", lastName: "Brightbrook", gender: "male", age: 37, profession: "physician", portrait: "portraits/amon_brightbrook.png", title: "Wandering Healer", stats: { attributes: { Strength: 11, Dexterity: 13, Intelligence: 18, Willpower: 16, Discipline: 17, Kindness: 18, Happiness: 14, Beauty: 12, Charisma: 15, Health: 87, Stamina: 82, Affection: 0 }, skills: { Academics: 18, Administration: 9, Medicine: 20, Science: 16, "Melee Combat": 6, "Ranged Combat": 7, Domestic: 11, Acrobatics: 8, Dancing: 8, Performance: 9, Gardening: 14, Survival: 12 }, states: { Fear: 3, Defiance: 4, Stress: 5, Respect: 15, Corruption: 0, Devotion: 10 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Pious", "Compassionate"], familyId: null } }, { id: "citizen_116", entryType: "immigration", immigrationKey: "elder_nora", immigrationTags: ["elder"], name: "Nora", lastName: "Whiteshield", gender: "female", age: 72, profession: "scribe", portrait: "portraits/nora_whiteshield.png", title: "Pilgrim Elder", stats: { attributes: { Strength: 6, Dexterity: 8, Intelligence: 18, Willpower: 19, Discipline: 18, Kindness: 19, Happiness: 13, Beauty: 8, Charisma: 16, Health: 68, Stamina: 55, Affection: 0 }, skills: { Academics: 19, Administration: 12, Medicine: 11, Science: 12, "Melee Combat": 3, "Ranged Combat": 4, Domestic: 10, Acrobatics: 3, Dancing: 5, Performance: 14, Gardening: 9, Survival: 10 }, states: { Fear: 1, Defiance: 3, Stress: 4, Respect: 19, Corruption: 0, Devotion: 17 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Pious", "Insightful"], familyId: null } }, { id: "citizen_117", entryType: "immigration", immigrationKey: "elder_thomas", immigrationTags: ["elder"], name: "Thomas", lastName: "Greer", gender: "male", age: 69, profession: "monk", portrait: "portraits/thomas_greer.png", title: "Pilgrim Elder", stats: { attributes: { Strength: 7, Dexterity: 9, Intelligence: 17, Willpower: 18, Discipline: 17, Kindness: 18, Happiness: 12, Beauty: 9, Charisma: 14, Health: 72, Stamina: 60, Affection: 0 }, skills: { Academics: 17, Administration: 11, Medicine: 10, Science: 10, "Melee Combat": 5, "Ranged Combat": 6, Domestic: 11, Acrobatics: 4, Dancing: 6, Performance: 12, Gardening: 10, Survival: 11 }, states: { Fear: 2, Defiance: 3, Stress: 4, Respect: 17, Corruption: 0, Devotion: 16 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Calm", "Observant"], familyId: null } }, { id: "citizen_118", entryType: "immigration", immigrationKey: "elder_helen", immigrationTags: ["elder"], name: "Helen", lastName: "Marrow", gender: "female", age: 71, profession: "singer", portrait: "portraits/helen_marrow.png", title: "Pilgrim Elder", stats: { attributes: { Strength: 6, Dexterity: 9, Intelligence: 16, Willpower: 17, Discipline: 16, Kindness: 18, Happiness: 14, Beauty: 10, Charisma: 17, Health: 70, Stamina: 58, Affection: 0 }, skills: { Academics: 14, Administration: 9, Medicine: 9, Science: 9, "Melee Combat": 3, "Ranged Combat": 4, Domestic: 12, Acrobatics: 4, Dancing: 12, Performance: 18, Gardening: 8, Survival: 9 }, states: { Fear: 2, Defiance: 3, Stress: 3, Respect: 18, Corruption: 0, Devotion: 15 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Gentle", "Faithful"], familyId: null } }, { id: "citizen_119", entryType: "immigration", immigrationKey: "laborer_bram", immigrationTags: ["laborer"], name: "Bram", lastName: "Copperfield", gender: "male", age: 30, profession: "miner", portrait: "portraits/bram_copperfield.png", title: "Stout Miner", stats: { attributes: { Strength: 17, Dexterity: 12, Intelligence: 11, Willpower: 13, Discipline: 13, Kindness: 12, Happiness: 13, Beauty: 10, Charisma: 11, Health: 94, Stamina: 95, Affection: 0 }, skills: { Academics: 6, Administration: 5, Medicine: 4, Science: 5, "Melee Combat": 12, "Ranged Combat": 7, Domestic: 8, Acrobatics: 8, Dancing: 6, Performance: 6, Gardening: 7, Survival: 12 }, states: { Fear: 3, Defiance: 5, Stress: 5, Respect: 11, Corruption: 0, Devotion: 5 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Tough", "Reliable"], familyId: null } }, { id: "citizen_120", entryType: "immigration", immigrationKey: "laborer_carys", immigrationTags: ["laborer"], name: "Carys", lastName: "Stone", gender: "female", age: 28, profession: "mason", portrait: "portraits/carys_stone.png", title: "Skilled Mason", stats: { attributes: { Strength: 15, Dexterity: 14, Intelligence: 12, Willpower: 13, Discipline: 14, Kindness: 13, Happiness: 14, Beauty: 12, Charisma: 13, Health: 92, Stamina: 90, Affection: 0 }, skills: { Academics: 7, Administration: 6, Medicine: 4, Science: 6, "Melee Combat": 9, "Ranged Combat": 7, Domestic: 9, Acrobatics: 9, Dancing: 8, Performance: 8, Gardening: 7, Survival: 10 }, states: { Fear: 3, Defiance: 4, Stress: 4, Respect: 12, Corruption: 0, Devotion: 6 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Dependable", "Sturdy"], familyId: null } } ]>>
<<script>> (function() { function deepClone(value) { return value && typeof value === 'object' ? JSON.parse(JSON.stringify(value)) : value; } function isPlainObject(value) { return value && typeof value === 'object' && !Array.isArray(value); } function mergeDefaults(template, source) { const output = deepClone(template); if (!source || typeof source !== 'object') { return output; } Object.keys(source).forEach(key => { const sourceValue = source[key]; if (isPlainObject(output[key]) && isPlainObject(sourceValue)) { output[key] = mergeDefaults(output[key], sourceValue); } else if (Array.isArray(output[key]) && Array.isArray(sourceValue)) { output[key] = sourceValue.slice(); } else if (sourceValue !== undefined) { output[key] = sourceValue; } }); Object.keys(source).forEach(key => { if (!(key in output)) { const value = source[key]; output[key] = isPlainObject(value) ? mergeDefaults({}, value) : Array.isArray(value) ? value.slice() : value; } }); return output; } const statusDefaults = { health: { value: 100, max: 100, min: 0, regenPerTick: 0, decayPerTick: 0, label: "Normal Health", icon: "Picx/Contents/GUI/Icons/Icon_StatusHealth.png" }, cleanliness: { value: 80, max: 100, min: 0, regenPerTick: 0, decayPerTick: 1, label: "Clean", icon: "Picx/Contents/GUI/Icons/Icon_StatusCleanliness.png" }, hunger: { value: 75, max: 100, min: 0, regenPerTick: 0, decayPerTick: 1, label: "Satisfied", icon: "Picx/Contents/GUI/Icons/Icon_StatusHunger.png" }, mood: { value: 70, max: 100, min: 0, regenPerTick: 0.5, decayPerTick: 0, label: "Content", icon: "Picx/Contents/GUI/Icons/Icon_StatusMood.png" } }; const kingdomResourceDefaults = { faith: { amount: 500000, icon: "Picx/Contents/GUI/Icons/Icon_ResourceFaith.png" }, influence: { amount: 500000, icon: "Picx/Contents/GUI/Icons/Icon_ResourceInfluence.png" } }; const playerResourceDefaults = { gold: 0, influence: 0, faith: 0 }; const cityResourceDefaults = { gold: 0, wood: 0, stone: 0, food: 0, population: 0 }; const defaultPlayerState = { identity: { id: "player", firstName: "", lastName: "", settlementName: "", race: "", gender: "male", age: 18, title: "", createdOnDay: 0, createdDate: null }, background: { family: "", childhood: "", teen: "", adult: "" }, creation: { remainingPoints: 65, remainingAttributePoints: 0, remainingTraitPoints: 0, selectedTraits: [], coaConfirmed: false, coa: { base: 1, pole: 1, pattern: 0, patternColor: 1, emblem: 0, primary: 1, secondary: 1 } }, stats: { attributes: { strength: 0, dexterity: 0, stamina: 0, intelligence: 0, stewardship: 0, constitution: 0, diplomacy: 0, martial: 0, manipulate: 0, charisma: 0, copulation: 0, kissing: 0 }, skills: { leadership: 0, meleeCombat: 0, rangedCombat: 0, administration: 0, diplomacy: 0, manipulate: 0, charm: 0, bondage: 0, kissing: 0 }, combat: { health: 10, maxHealth: 10, stamina: 100, maxStamina: 100, armor: 0, damage: 0, criticalChance: 0, dodgeChance: 0 }, combatSkills: { selected: "" } }, status: { health: { value: 100, max: 100, min: 0, regenPerHour: 0, decayPerHour: 0, label: "Health", icon: "Picx/Contents/GUI/Icons/Icon_StatusHealth.png" }, cleanliness: { value: 100, max: 100, min: 0, regenPerHour: 0, decayPerHour: 4, dailyDecay: 0, label: "Cleanliness", icon: "Picx/Contents/GUI/Icons/Icon_StatusCleanliness.png" }, hunger: { value: 100, max: 100, min: 0, regenPerHour: 0, decayPerHour: 5, dailyDecay: 0, label: "Hunger", icon: "Picx/Contents/GUI/Icons/Icon_StatusHunger.png" }, stamina: { value: 100, max: 100, min: 0, regenPerHour: 0, decayPerHour: 3, dailyDecay: 0, label: "Stamina", icon: "Picx/Contents/GUI/Icons/Icon_StatusStamina.png" } }, resources: deepClone(playerResourceDefaults), relationships: { wife: "", concubines: 0, children: 0 }, ui: { isShowingTraits: false, manualControlsPosition: "overview", currentSettlementTab: "overview" }, roster: { character: null, characterDatabase: [] }, market: { version: 2, hubs: {}, citizenQueue: [], reports: [], demandSignals: [], supplyRecords: [], purchaseHistory: [], demandFeed: [] }, buildingActions: { dailyUseCount: {}, totalUseCount: {}, lastResetDay: 1 } }; function ensureKingdomResources() { if (!State.variables.kingdomResources) { State.variables.kingdomResources = deepClone(kingdomResourceDefaults); return; } Object.entries(kingdomResourceDefaults).forEach(([key, defaults]) => { if (!State.variables.kingdomResources[key]) { State.variables.kingdomResources[key] = deepClone(defaults); return; } const resource = State.variables.kingdomResources[key]; if (typeof resource.amount !== "number") { resource.amount = defaults.amount; } if (!resource.icon) { resource.icon = defaults.icon; } }); } function ensureCityResources() { if (!State.variables.resources) { State.variables.resources = Object.assign({}, cityResourceDefaults); return; } Object.entries(cityResourceDefaults).forEach(([key, value]) => { if (typeof State.variables.resources[key] !== "number") { State.variables.resources[key] = value; } }); } function ensureMarketData() { if (!State.variables.marketData || typeof State.variables.marketData !== "object") { State.variables.marketData = deepClone(defaultPlayerState.market); return; } State.variables.marketData = mergeDefaults(defaultPlayerState.market, State.variables.marketData); } function migrateLegacyPlayerState(state) { const output = mergeDefaults(defaultPlayerState, state || {}); const aliasCollector = new Set(Array.isArray(output.identity.aliasIds) ? output.identity.aliasIds.map(String) : []); const registerAlias = value => { if (typeof value !== "string") { return; } const trimmed = value.trim(); if (!trimmed || trimmed === "player") { return; } aliasCollector.add(trimmed); }; if (state && state.identity && typeof state.identity.id === "string") { registerAlias(state.identity.id); } registerAlias(State.variables.playerId); registerAlias(State.variables.playerID); if (Array.isArray(State.variables.playerIdAliases)) { State.variables.playerIdAliases.forEach(registerAlias); } if (State.variables.playerCharacter && typeof State.variables.playerCharacter.id === "string") { registerAlias(State.variables.playerCharacter.id); } if (State.variables.character && typeof State.variables.character.id === "string") { registerAlias(State.variables.character.id); } if (typeof output.identity.id !== "string" || !output.identity.id.trim()) { output.identity.id = "player"; } else { const trimmed = output.identity.id.trim(); if (trimmed !== "player") { registerAlias(trimmed); output.identity.id = "player"; } } if (aliasCollector.size > 0) { output.identity.aliasIds = Array.from(aliasCollector); } else { delete output.identity.aliasIds; } if (State.variables.firstName !== undefined) { output.identity.firstName = State.variables.firstName; } if (State.variables.lastName !== undefined) { output.identity.lastName = State.variables.lastName; } if (State.variables.settlementName !== undefined) { output.identity.settlementName = State.variables.settlementName; } if (State.variables.characterRace !== undefined) { output.identity.race = State.variables.characterRace; } if (State.variables.gender !== undefined) { output.identity.gender = State.variables.gender; } if (State.variables.playerGender !== undefined) { output.identity.gender = State.variables.playerGender; } if (typeof State.variables.age === "number") { output.identity.age = State.variables.age; } if (State.variables.title !== undefined) { output.identity.title = State.variables.title; } output.background.family = State.variables.familyBackground || output.background.family; output.background.childhood = State.variables.childhoodTrait || output.background.childhood; output.background.teen = State.variables.teenActivity || output.background.teen; output.background.adult = State.variables.adultAchievement || output.background.adult; if (typeof State.variables.remainingCharacterPoints === "number") { output.creation.remainingPoints = State.variables.remainingCharacterPoints; } if (typeof State.variables.remainingAttributePoints === "number") { output.creation.remainingAttributePoints = State.variables.remainingAttributePoints; } if (typeof State.variables.remainingTraitPoints === "number") { output.creation.remainingTraitPoints = State.variables.remainingTraitPoints; } if (Array.isArray(State.variables.selectedTraits)) { output.creation.selectedTraits = State.variables.selectedTraits.slice(); } if (State.variables.attributes) { output.stats.attributes = mergeDefaults(defaultPlayerState.stats.attributes, State.variables.attributes); } if (State.variables.skills) { output.stats.skills = mergeDefaults(defaultPlayerState.stats.skills, State.variables.skills); } if (State.variables.combatSkills) { output.stats.combatSkills = mergeDefaults(defaultPlayerState.stats.combatSkills, State.variables.combatSkills); } if (State.variables.character && typeof State.variables.character === "object") { output.roster.character = State.variables.character; } if (Array.isArray(State.variables.characterDatabase)) { output.roster.characterDatabase = State.variables.characterDatabase.slice(); } if (State.variables.marketData && typeof State.variables.marketData === "object") { output.market = State.variables.marketData; } if (State.variables.playerResources) { output.resources = mergeDefaults(playerResourceDefaults, State.variables.playerResources); } if (typeof State.variables.influence === "number") { output.resources.influence = State.variables.influence; } output.relationships.wife = State.variables.wife || output.relationships.wife; output.relationships.concubines = typeof State.variables.concubines === "number" ? State.variables.concubines : output.relationships.concubines; output.relationships.children = typeof State.variables.children === "number" ? State.variables.children : output.relationships.children; if (typeof State.variables.isShowingTraits === "boolean") { output.ui.isShowingTraits = State.variables.isShowingTraits; } if (State.variables.manualControlsPosition) { output.ui.manualControlsPosition = State.variables.manualControlsPosition; } if (State.variables.currentSettlementTab) { output.ui.currentSettlementTab = State.variables.currentSettlementTab; } return output; } function syncPlayerAliases(playerState) { const identity = playerState.identity; State.variables.playerId = identity.id; if (Array.isArray(identity.aliasIds) && identity.aliasIds.length > 0) { State.variables.playerIdAliases = identity.aliasIds.slice(); } else { delete State.variables.playerIdAliases; } State.variables.firstName = identity.firstName; State.variables.lastName = identity.lastName; State.variables.settlementName = identity.settlementName; State.variables.characterRace = identity.race; State.variables.gender = identity.gender; State.variables.age = identity.age; State.variables.title = identity.title; const background = playerState.background; State.variables.familyBackground = background.family; State.variables.childhoodTrait = background.childhood; State.variables.teenActivity = background.teen; State.variables.adultAchievement = background.adult; const creation = playerState.creation; State.variables.remainingCharacterPoints = creation.remainingPoints; State.variables.remainingAttributePoints = creation.remainingAttributePoints; State.variables.remainingTraitPoints = creation.remainingTraitPoints; State.variables.selectedTraits = creation.selectedTraits; const stats = playerState.stats; State.variables.attributes = stats.attributes; State.variables.skills = stats.skills; State.variables.combatSkills = stats.combatSkills; State.variables.character = playerState.roster.character; State.variables.characterDatabase = playerState.roster.characterDatabase; State.variables.playerResources = playerState.resources; State.variables.influence = playerState.resources.influence; State.variables.wife = playerState.relationships.wife; State.variables.concubines = playerState.relationships.concubines; State.variables.children = playerState.relationships.children; State.variables.isShowingTraits = playerState.ui.isShowingTraits; State.variables.manualControlsPosition = playerState.ui.manualControlsPosition; State.variables.currentSettlementTab = playerState.ui.currentSettlementTab; } function ensurePlayerState(reset) { ensureMarketData(); const existing = reset ? null : State.variables.playerState; const migrated = migrateLegacyPlayerState(existing); if (existing && !reset && isPlainObject(existing)) { Object.keys(migrated).forEach(key => { existing[key] = migrated[key]; }); State.variables.playerState = existing; } else { State.variables.playerState = migrated; } const stateRef = State.variables.playerState; if (!isPlainObject(stateRef.resources)) { stateRef.resources = deepClone(playerResourceDefaults); } else { stateRef.resources = mergeDefaults(playerResourceDefaults, stateRef.resources); } stateRef.market = State.variables.marketData; syncPlayerAliases(stateRef); return stateRef; } function bindPlayerStateAccessors() { const stateVars = State.variables; const accessors = { playerId: { get: ps => ps.identity.id, set: (ps, value) => { const trimmed = typeof value === "string" ? value.trim() : ""; if (trimmed && trimmed !== "player") { const aliases = Array.isArray(ps.identity.aliasIds) ? ps.identity.aliasIds.slice() : []; if (!aliases.includes(trimmed)) { aliases.push(trimmed); } ps.identity.aliasIds = aliases; } ps.identity.id = "player"; } }, firstName: { get: ps => ps.identity.firstName, set: (ps, value) => { ps.identity.firstName = value || ''; } }, lastName: { get: ps => ps.identity.lastName, set: (ps, value) => { ps.identity.lastName = value || ''; } }, settlementName: { get: ps => ps.identity.settlementName, set: (ps, value) => { ps.identity.settlementName = value || ''; } }, characterRace: { get: ps => ps.identity.race, set: (ps, value) => { ps.identity.race = value || ''; } }, gender: { get: ps => ps.identity.gender, set: (ps, value) => { ps.identity.gender = value || ''; } }, age: { get: ps => ps.identity.age, set: (ps, value) => { const num = Number(value); if (Number.isFinite(num)) { ps.identity.age = num; } } }, title: { get: ps => ps.identity.title, set: (ps, value) => { ps.identity.title = value || ''; } }, playerIdAliases: { get: ps => Array.isArray(ps.identity.aliasIds) ? ps.identity.aliasIds.slice() : [], set: (ps, value) => { if (!Array.isArray(value)) { ps.identity.aliasIds = undefined; return; } const filtered = value .filter(entry => typeof entry === "string") .map(entry => entry.trim()) .filter(entry => entry && entry !== "player"); const unique = Array.from(new Set(filtered)); ps.identity.aliasIds = unique.length > 0 ? unique : undefined; } }, familyBackground: { get: ps => ps.background.family, set: (ps, value) => { ps.background.family = value || ''; } }, childhoodTrait: { get: ps => ps.background.childhood, set: (ps, value) => { ps.background.childhood = value || ''; } }, teenActivity: { get: ps => ps.background.teen, set: (ps, value) => { ps.background.teen = value || ''; } }, adultAchievement: { get: ps => ps.background.adult, set: (ps, value) => { ps.background.adult = value || ''; } }, remainingCharacterPoints: { get: ps => ps.creation.remainingPoints, set: (ps, value) => { const num = Number(value); if (Number.isFinite(num)) { ps.creation.remainingPoints = num; } } }, remainingAttributePoints: { get: ps => ps.creation.remainingAttributePoints, set: (ps, value) => { const num = Number(value); if (Number.isFinite(num)) { ps.creation.remainingAttributePoints = num; } } }, remainingTraitPoints: { get: ps => ps.creation.remainingTraitPoints, set: (ps, value) => { const num = Number(value); if (Number.isFinite(num)) { ps.creation.remainingTraitPoints = num; } } }, selectedTraits: { get: ps => ps.creation.selectedTraits, set: (ps, value) => { ps.creation.selectedTraits = Array.isArray(value) ? value.slice() : []; } }, attributes: { get: ps => ps.stats.attributes, set: (ps, value) => { ps.stats.attributes = isPlainObject(value) ? mergeDefaults(defaultPlayerState.stats.attributes, value) : deepClone(defaultPlayerState.stats.attributes); } }, skills: { get: ps => ps.stats.skills, set: (ps, value) => { ps.stats.skills = isPlainObject(value) ? mergeDefaults(defaultPlayerState.stats.skills, value) : deepClone(defaultPlayerState.stats.skills); } }, combatSkills: { get: ps => ps.stats.combatSkills, set: (ps, value) => { ps.stats.combatSkills = isPlainObject(value) ? mergeDefaults(defaultPlayerState.stats.combatSkills, value) : deepClone(defaultPlayerState.stats.combatSkills); } }, character: { get: ps => ps.roster.character, set: (ps, value) => { ps.roster.character = value || null; } }, characterDatabase: { get: ps => ps.roster.characterDatabase, set: (ps, value) => { ps.roster.characterDatabase = Array.isArray(value) ? value.slice() : []; } }, playerResources: { get: ps => ps.resources, set: (ps, value) => { ps.resources = mergeDefaults(playerResourceDefaults, isPlainObject(value) ? value : {}); } }, influence: { get: ps => ps.resources.influence, set: (ps, value) => { const num = Number(value); if (Number.isFinite(num)) { ps.resources.influence = num; } } }, wife: { get: ps => ps.relationships.wife, set: (ps, value) => { ps.relationships.wife = value || ''; } }, concubines: { get: ps => ps.relationships.concubines, set: (ps, value) => { const num = Number(value); if (Number.isFinite(num)) { ps.relationships.concubines = num; } } }, children: { get: ps => ps.relationships.children, set: (ps, value) => { const num = Number(value); if (Number.isFinite(num)) { ps.relationships.children = num; } } }, isShowingTraits: { get: ps => ps.ui.isShowingTraits, set: (ps, value) => { ps.ui.isShowingTraits = !!value; } }, manualControlsPosition: { get: ps => ps.ui.manualControlsPosition, set: (ps, value) => { ps.ui.manualControlsPosition = value || 'overview'; } }, currentSettlementTab: { get: ps => ps.ui.currentSettlementTab, set: (ps, value) => { ps.ui.currentSettlementTab = value || 'overview'; } } }; Object.entries(accessors).forEach(([prop, handlers]) => { try { delete stateVars[prop]; } catch (error) { /* ignore */ } Object.defineProperty(stateVars, prop, { configurable: true, enumerable: true, get() { const ps = State.variables.playerState; return ps ? handlers.get(ps) : undefined; }, set(value) { const ps = State.variables.playerState; if (!ps) { return; } handlers.set(ps, value); } }); }); } const globalSetup = (function() { if (typeof window !== 'undefined') { window.setup = window.setup || {}; return window.setup; } if (typeof setup === 'object' && setup !== null) { return setup; } return {}; })(); if (typeof window !== 'undefined') { window.setup = globalSetup; } if (typeof setup === 'undefined' || setup !== globalSetup) { try { setup = globalSetup; } catch (error) { /* ignore assignment issues */ } } globalSetup.syncPlayerAliases = function() { if (State.variables.playerState) { syncPlayerAliases(State.variables.playerState); } }; globalSetup.ensurePlayerState = function(reset) { return ensurePlayerState(!!reset); }; globalSetup.rebindPlayerStateAccessors = function() { bindPlayerStateAccessors(); }; globalSetup.playerState = { get() { return State.variables.playerState || globalSetup.ensurePlayerState(false); }, update(updater) { const state = globalSetup.ensurePlayerState(false); if (typeof updater === 'function') { updater(state); } else if (isPlainObject(updater)) { Object.assign(state, updater); } syncPlayerAliases(state); return state; }, reset() { return globalSetup.ensurePlayerState(true); } }; ensureKingdomResources(); ensureCityResources(); ensureMarketData(); globalSetup.ensurePlayerState(false); bindPlayerStateAccessors(); $(document).on(':passagestart', function() { if (!State.variables.playerState) { globalSetup.ensurePlayerState(false); } bindPlayerStateAccessors(); }); })(); <</script>>
<<run setup.itemCatalog = (window.GameItemCatalog && Array.isArray(window.GameItemCatalog.items)) ? window.GameItemCatalog.items : []>>
<div class="character_status"> <div class="status_panel" onclick="window.toggleStatusTooltip()"> <div class="status_header"><span>Current State</span></div> <div class="status_info"> <div class="status_row"> <span class="status_bullet">•</span> <span class="status_label" data-status-type="health"> <<= window.getStatusText && State.variables.playerState && State.variables.playerState.status && State.variables.playerState.status.health ? window.getStatusText('health', State.variables.playerState.status.health.value) : "Health" >> </span> <span class="status_bullet">•</span> <span class="status_label" data-status-type="cleanliness"> <<= window.getStatusText && State.variables.playerState && State.variables.playerState.status && State.variables.playerState.status.cleanliness ? window.getStatusText('cleanliness', State.variables.playerState.status.cleanliness.value) : "Cleanliness" >> </span> </div> <div class="status_row"> <span class="status_bullet">•</span> <span class="status_label" data-status-type="hunger"> <<= window.getStatusText && State.variables.playerState && State.variables.playerState.status && State.variables.playerState.status.hunger ? window.getStatusText('hunger', State.variables.playerState.status.hunger.value) : "Hunger" >> </span> <span class="status_bullet">•</span> <span class="status_label" data-status-type="stamina"> <<= window.getStatusText && State.variables.playerState && State.variables.playerState.status && State.variables.playerState.status.stamina ? window.getStatusText('stamina', State.variables.playerState.status.stamina.value) : "Stamina" >> </span> </div> <div class="resources_row"> <div class="resource_item"> <img src="Bin/Contents/UI/Icons/Icon_ResourceFaith.png" alt="faith"> <span class="resource_value"><<= ((State.variables.playerState && State.variables.playerState.resources && typeof State.variables.playerState.resources.faith === 'number') ? State.variables.playerState.resources.faith : 0).toLocaleString() >></span> </div> <div class="resource_item"> <img src="Bin/Contents/UI/Icons/Icon_ResourceInfluence.png" alt="influence"> <span class="resource_value"><<= ((State.variables.playerState && State.variables.playerState.resources && typeof State.variables.playerState.resources.influence === 'number') ? State.variables.playerState.resources.influence : 0).toLocaleString() >></span> </div> <div class="resource_item"> <img src="Bin/Contents/UI/Icons/Icon_ResourceGold.png" alt="gold"> <span class="resource_value"><<= ((State.variables.playerState && State.variables.playerState.resources && typeof State.variables.playerState.resources.gold === 'number') ? State.variables.playerState.resources.gold : 0).toLocaleString() >></span> </div> </div> </div> </div> </div>
<<set $slaveCatalog = [ { id: "slave_template_bandit_male", name: "Captured Bandit", lastName: "", gender: "male", portrait: "portraits/slaves/bandit_male.png", title: "Captured Raider", origin: "Bandit", stats: { attributes: { Strength: 15, Dexterity: 13, Intelligence: 9, Willpower: 11, Discipline: 8, Kindness: 4, Happiness: 6, Beauty: 10, Charisma: 9, Health: 85, Stamina: 80, Affection: 0 }, skills: { Academics: 3, Administration: 2, Medicine: 1, Science: 1, "Melee Combat": 14, "Ranged Combat": 8, Domestic: 2, Acrobatics: 10, Dancing: 1, Performance: 2, Gardening: 1, Survival: 13 }, states: { Fear: 6, Defiance: 14, Stress: 8, Respect: 3, Corruption: 5, Devotion: 1 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Aggressive", "Unruly"], familyId: null } }, { id: "slave_template_bandit_female", name: "Captured Huntress", lastName: "", gender: "female", portrait: "portraits/slaves/bandit_female.png", title: "Captured Raider", origin: "Bandit", stats: { attributes: { Strength: 12, Dexterity: 16, Intelligence: 10, Willpower: 12, Discipline: 9, Kindness: 6, Happiness: 7, Beauty: 13, Charisma: 12, Health: 82, Stamina: 78, Affection: 0 }, skills: { Academics: 4, Administration: 2, Medicine: 2, Science: 2, "Melee Combat": 11, "Ranged Combat": 14, Domestic: 3, Acrobatics: 12, Dancing: 4, Performance: 3, Gardening: 2, Survival: 12 }, states: { Fear: 7, Defiance: 12, Stress: 9, Respect: 4, Corruption: 4, Devotion: 2 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Cunning", "Observant"], familyId: null } }, { id: "slave_template_soldier_male", name: "Captured Soldier", lastName: "", gender: "male", portrait: "portraits/slaves/soldier_male.png", title: "Defeated Soldier", origin: "Militia", stats: { attributes: { Strength: 16, Dexterity: 12, Intelligence: 11, Willpower: 14, Discipline: 15, Kindness: 7, Happiness: 5, Beauty: 11, Charisma: 10, Health: 90, Stamina: 88, Affection: 0 }, skills: { Academics: 6, Administration: 4, Medicine: 3, Science: 2, "Melee Combat": 16, "Ranged Combat": 10, Domestic: 2, Acrobatics: 9, Dancing: 2, Performance: 3, Gardening: 2, Survival: 11 }, states: { Fear: 5, Defiance: 9, Stress: 7, Respect: 8, Corruption: 2, Devotion: 4 } }, social: { relationshipStatus: "married", partnerId: null, children: [], traits: ["Disciplined", "Stoic"], familyId: null } }, { id: "slave_template_soldier_female", name: "Captured Scout", lastName: "", gender: "female", portrait: "portraits/slaves/soldier_female.png", title: "Defeated Scout", origin: "Militia", stats: { attributes: { Strength: 11, Dexterity: 17, Intelligence: 12, Willpower: 13, Discipline: 14, Kindness: 8, Happiness: 6, Beauty: 12, Charisma: 11, Health: 84, Stamina: 86, Affection: 0 }, skills: { Academics: 5, Administration: 3, Medicine: 4, Science: 3, "Melee Combat": 10, "Ranged Combat": 15, Domestic: 3, Acrobatics: 13, Dancing: 5, Performance: 4, Gardening: 3, Survival: 14 }, states: { Fear: 6, Defiance: 8, Stress: 8, Respect: 9, Corruption: 1, Devotion: 5 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Alert", "Quick"], familyId: null } } ]>> <<script>> if (!setup.slaveCatalog || !Array.isArray(setup.slaveCatalog)) { setup.slaveCatalog = $slaveCatalog; } else { setup.slaveCatalog = setup.slaveCatalog.concat($slaveCatalog); } if (!setup.slaveCatalogById) { setup.slaveCatalogById = {}; } setup.slaveCatalog.forEach(function(entry) { if (!entry || !entry.id) { return; } setup.slaveCatalogById[entry.id] = entry; }); if (!setup.getSlaveTemplate) { setup.getSlaveTemplate = function(templateId) { if (!templateId) { return null; } return setup.slaveCatalogById ? setup.slaveCatalogById[templateId] || null : null; }; } if (!setup.getSlaveTemplatesByGender) { setup.getSlaveTemplatesByGender = function(gender) { if (!setup.slaveCatalog || !Array.isArray(setup.slaveCatalog)) { return []; } return setup.slaveCatalog.filter(function(entry) { if (!entry) { return false; } if (!gender) { return true; } return !entry.gender || entry.gender === gender; }); }; } <</script>>
<<run setup.combatSkillDefinitions = { "Move": { name: "Move", icon: "Bin/Contents/Combat/Skills/Basic/BA_Move.png", type: "support", description: "Move to a new position on the battlefield.", cooldown: 0, range: 0, effect: function(caster, target, combatSystem) { combatSystem.showMessage(`Select where to move ${caster.name}`); } }, "Throwing Knife": { name: "Throwing Knife", icon: "Bin/Contents/Combat/Skills/Icons/Skill_DaggerThrow_nb.png", type: "combat", description: "Hurls a sharp knife at medium range. Higher damage against isolated targets.", cooldown: 2, range: 4, requiresSkillTags: ["requiresDominantHand", "ranged"], requiresLimbTags: ["grasp"], limbUsage: { dominant: 1 }, targetArea: function(target, context, combatSystem) { return combatSystem.getDominantHand(target); }, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target for throwing knife!`); return; } // Base throwing knife damage const baseDamage = Math.floor((caster.attack || 1) * 0.8); // Bonus damage if target is isolated (no adjacent allies) const adjacentAllies = combatSystem.getAdjacentAllies(target); const isolationBonus = adjacentAllies.length === 0 ? 1.3 : 1.0; const finalDamage = Math.floor(baseDamage * isolationBonus); // Apply damage target.hp = Math.max(0, target.hp - finalDamage); if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('basicAttack', { caster: caster, target: target, damage: finalDamage, isCritical: false }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(finalDamage, target, { isCritical: false }); } // Visual feedback const isolationMessage = isolationBonus > 1 ? " (Isolated target!)" : ""; combatSystem.showMessage(`${caster.name} throws a knife at ${target.name} for ${finalDamage} damage!${isolationMessage}`); } }, /* === UNIVERSAL SKILLS === */ "Basic Attack": { name: "Basic Attack", icon: "Bin/Contents/Combat/Skills/Icons/Warriorskill_01_nobg.png", type: "combat", description: "Standard attack with no special effects.", cooldown: 0, range: 1, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target to attack!`); return; } const damage = caster.attack || 1; target.hp = Math.max(0, target.hp - damage); if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('basicAttack', { caster: caster, target: target, damage: damage, isCritical: false }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(damage, target, { isCritical: false }); } combatSystem.showMessage(`${caster.name} attacks ${target.name} for ${damage} damage`); } }, "Basic Slash": { name: "Basic Slash", icon: "Bin/Contents/Combat/Skills/Basic/BA_Attack.png", type: "combat", description: "Standard sword slash that adds a small crit bonus.", cooldown: 0, range: 1, targetArea: function(target, context, combatSystem) { return combatSystem.getDominantHand(target); }, injuryImpact: { baseChance: 0.18, damageScaling: 0.35, severityThresholds: [ { minRatio: 0.45, severity: "moderate" }, { minRatio: 0.65, severity: "major" } ], allowEscalation: false, allowCatastrophic: false, durations: { minor: 2, moderate: 3, major: 4, default: 3 }, durationVariance: 1, penalties: { minor: { attack_flat: -1 }, moderate: { attack_flat: -2, critChance_flat: -0.05 }, major: { attack_flat: -3, critChance_flat: -0.1 }, default: { attack_flat: -1 } } }, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target to slash!`); return; } const baseDamage = caster.attack || 1; const critChance = (caster.critChance || 0) + 0.1; const critRoll = Math.random(); const damage = critRoll < critChance ? Math.floor(baseDamage * 1.5) : baseDamage; target.hp = Math.max(0, target.hp - damage); if (critRoll < critChance) { if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('criticalHit', { caster: caster, target: target, damage: damage }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(damage, target, { isCritical: true }); } combatSystem.showMessage(`${caster.name} lands a critical slash on ${target.name} for ${damage} damage!`); } else { if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('basicAttack', { caster: caster, target: target, damage: damage, isCritical: false }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(damage, target, { isCritical: false }); } combatSystem.showMessage(`${caster.name} slashes ${target.name} for ${damage} damage.`); } const durabilityContext = (window.DurabilitySystem && typeof window.DurabilitySystem.evaluateUnitWeapon === 'function') ? window.DurabilitySystem.evaluateUnitWeapon(caster) : null; if (combatSystem && typeof combatSystem.applyWeaponWear === 'function') { combatSystem.applyWeaponWear(caster, 1, { record: durabilityContext ? durabilityContext.record : null, itemName: durabilityContext && durabilityContext.item ? durabilityContext.item.name : null }); } } }, "Improvised Kick": { name: "Improvised Kick", icon: "Bin/Contents/Combat/Skills/Basic/BA_Kick.png", type: "combat", description: "A close-range kick used when the dominant hand is compromised.", cooldown: 0, range: 1, requiresDominantHand: false, requiresSkillTags: ["requiresMovement"], requiresLimbTags: ["movement"], limbUsage: { movement: 1 }, effect: function(caster, target, combatSystem) { if (!target || target.team === caster.team) { combatSystem.showMessage(`${caster.name} needs an enemy in reach to kick!`); return; } const rawAttack = caster.attack || 1; const baseDamage = Math.max(2, Math.floor(rawAttack * 0.6)); target.hp = Math.max(0, target.hp - baseDamage); if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('kick', { caster: caster, target: target, damage: baseDamage, isCritical: false }); } else { if (window.AudioEvents && typeof window.AudioEvents.emit === 'function') { window.AudioEvents.emit('combat:sfx', { id: 'combat_kick_hit', volume: 0.6 }); } else if (window.AudioManager && typeof window.AudioManager.playSFX === 'function') { window.AudioManager.playSFX('combat_kick_hit', 0.6); } if (combatSystem.animationManager && caster.companionId) { combatSystem.animationManager.playAnimation(caster, 'Kick'); } if (combatSystem.animationManager && target.companionId) { combatSystem.animationManager.playAnimation(target, 'Damaged'); } if (window.playCombatUnitVoice) { window.playCombatUnitVoice(caster, 'kick'); window.playCombatUnitVoice(target, 'bluntDamage'); } combatSystem.displayDamageNumber(baseDamage, target.x, target.y, false); } combatSystem.showMessage(`${caster.name} kicks ${target.name} for ${baseDamage} damage!`); if (target.hp <= 0) { combatSystem.processDeath(target, caster); } } }, "Heavy Strike": { name: "Heavy Strike", icon: "Bin/Contents/Combat/Skills/Basic/BA_Attack.png", type: "combat", description: "More damage but can't move next turn.", cooldown: 0, range: 1, requiresSkillTags: ["requiresDominantHand", "requiresTwoHands"], requiresLimbTags: ["grasp"], limbUsage: { dominant: 1, offhand: 1 }, targetArea: function(target, context, combatSystem) { return combatSystem.getDominantHand(target); }, injuryImpact: { baseChance: 0.26, damageScaling: 0.55, severityThresholds: [ { minRatio: 0.34, severity: "moderate" }, { minRatio: 0.55, severity: "major" }, { minRatio: 0.7, severity: "critical" } ], catastrophicThreshold: "major", catastrophic: { baseDifficulty: 0.52, armorWeight: 0.024, preventBonus: 0.6, stabilizedBonus: 0.36, stabilizedFlagBonus: 0.26, onFailStatus: "missing", onFailSeverity: "permanent", onSaveStatus: "maimed", onSaveSeverity: "permanent", durations: { missing: 28, maimed: 20 } }, durations: { minor: 3, moderate: 4, major: 5, critical: 6, default: 4 }, durationVariance: 1, penalties: { minor: { defense_flat: -1 }, moderate: { defense_flat: -2, attack_flat: -1 }, major: { defense_flat: -3, attack_flat: -2 }, critical: { defense_flat: -4, attack_flat: -3 }, default: { defense_flat: -1 } } }, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target for Heavy Strike!`); return; } // RISK: Lock movement next turn caster.cantMoveNextTurn = true; // REWARD: More damage const damage = Math.floor((caster.attack || 1) * 1.5); target.hp = Math.max(0, target.hp - damage); if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('basicAttack', { caster: caster, target: target, damage: damage, isCritical: false }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(damage, target, { isCritical: false }); } combatSystem.showMessage(`${caster.name} heavy strikes ${target.name} for ${damage} damage! ${caster.name} can't move next turn.`); const durabilityContext = (window.DurabilitySystem && typeof window.DurabilitySystem.evaluateUnitWeapon === 'function') ? window.DurabilitySystem.evaluateUnitWeapon(caster) : null; if (combatSystem && typeof combatSystem.applyWeaponWear === 'function') { combatSystem.applyWeaponWear(caster, 2, { record: durabilityContext ? durabilityContext.record : null, itemName: durabilityContext && durabilityContext.item ? durabilityContext.item.name : null }); } } }, "Shield Break": { name: "Shield Break", icon: "Bin/Contents/Combat/Skills/Icons/Skill_ShieldDrop_nb.png", type: "combat", description: "Reduced damage but target takes extra damage from allies.", cooldown: 0, range: 1, targetArea: ["leftArm", "rightArm"], injuryImpact: { baseChance: 0.2, damageScaling: 0.45, severityThresholds: [ { minRatio: 0.4, severity: "moderate" }, { minRatio: 0.65, severity: "major" } ], durations: { minor: 2, moderate: 3, major: 4, critical: 5, default: 3 }, penalties: { minor: { defense_flat: -1 }, moderate: { defense_flat: -2 }, major: { defense_flat: -3, attack_flat: -1 }, critical: { defense_flat: -4, attack_flat: -2 }, default: { defense_flat: -1 } } }, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target to break shields!`); return; } // RISK: Lower damage const damage = Math.floor((caster.attack || 1) * 0.7); target.hp = Math.max(0, target.hp - damage); if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('basicAttack', { caster: caster, target: target, damage: damage, isCritical: false }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(damage, target, { isCritical: false }); } // CASCADE: Set up for team target.exposed = 2; // Lasts 2 turns, allies get bonus damage combatSystem.showMessage(`${caster.name} breaks ${target.name}'s defenses for ${damage} damage! Target is exposed!`); } }, "Quickstep": { name: "Quickstep", icon: "Bin/Contents/Combat/Skills/Icons/Skill_Dodge_nb.png", type: "support", description: "Extra movement but become vulnerable.", cooldown: 2, range: 0, effect: function(caster, target, combatSystem) { // RISK: Take extra damage caster.vulnerable = 1; // REWARD: Extra movement this turn caster.bonusMovement = 2; caster.canMove = true; // Can move again this turn if (window.AudioEvents && typeof window.AudioEvents.emit === 'function') { window.AudioEvents.emit('combat:sfx', { id: 'combat_skill_inspire_mixed', volume: 0.55 }); } else if (window.AudioManager && typeof window.AudioManager.playSFX === 'function') { window.AudioManager.playSFX('combat_skill_inspire_mixed', 0.55); } combatSystem.showMessage(`${caster.name} gains extra movement but becomes vulnerable!`); } }, "Defensive Stance": { name: "Defensive Stance", icon: "Bin/Contents/Combat/Skills/Icons/Skill_ShieldBlock_nb.png", type: "support", description: "Increase defense but can't attack next turn.", cooldown: 1, range: 0, effect: function(caster, target, combatSystem) { // RISK: Can't attack next turn caster.cantAttackNextTurn = true; // REWARD: Strong defense boost caster.defenseBonus = Math.floor((caster.defense || 1) * 0.5); caster.defensiveStance = 2; // Lasts 2 turns if (typeof combatSystem.addEffect === 'function') { combatSystem.addEffect(caster, 'Defensive Stance', { type: "status", status: "defensive_stance_guard", duration: 2, preventCatastrophic: true, stabilizedLimbs: ["torso", "leftArm", "rightArm"], limbMitigation: { torso: 0.08, leftArm: 0.06, rightArm: 0.06 } }); } if (window.AudioEvents && typeof window.AudioEvents.emit === 'function') { window.AudioEvents.emit('combat:sfx', { id: 'combat_skill_inspire_mixed', volume: 0.55 }); } else if (window.AudioManager && typeof window.AudioManager.playSFX === 'function') { window.AudioManager.playSFX('combat_skill_inspire_mixed', 0.55); } combatSystem.showMessage(`${caster.name} takes defensive stance! +50% defense but can't attack next turn.`); } }, /* === KNIGHT SKILLS === */ "Shield Bash": { name: "Shield Bash", icon: "Bin/Contents/Combat/Skills/Basic/BA_DefStance.png", type: "combat", description: "RISK: Take retaliation damage. CASCADE: Stuns enemy + pushes for ally positioning.", cooldown: 3, range: 1, targetArea: ["head", "torso"], injuryImpact: { baseChance: 0.24, damageScaling: 0.4, severityThresholds: [ { minRatio: 0.32, severity: "moderate" }, { minRatio: 0.5, severity: "major" }, { minRatio: 0.68, severity: "critical" } ], catastrophicThreshold: "major", catastrophic: { baseDifficulty: 0.46, armorWeight: 0.02, preventBonus: 0.6, stabilizedBonus: 0.32, stabilizedFlagBonus: 0.24, onFailStatus: "maimed", onFailSeverity: "permanent", onSaveStatus: "injured", onSaveSeverity: "major", durations: { maimed: 16 } }, durations: { minor: 2, moderate: 3, major: 4, critical: 5, default: 3 }, penalties: { minor: { defense_flat: -1 }, moderate: { defense_flat: -1, movement_flat: -1 }, major: { defense_flat: -2, movement_flat: -1, critChance_flat: -0.05 }, critical: { defense_flat: -3, movement_flat: -2, critChance_flat: -0.1 }, default: { defense_flat: -1 } } }, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target to shield bash!`); return; } // RISK: Take retaliation damage (direct HP manipulation) const retaliation = Math.floor((target.attack || 1) * 0.3); caster.hp = Math.max(0, caster.hp - retaliation); // REWARD + CASCADE: Damage, stun and reposition (direct HP manipulation) const damage = caster.attack || 1; target.hp = Math.max(0, target.hp - damage); if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('skillWithKickback', { caster: caster, target: target, damage: damage, kickbackDamage: retaliation }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(damage, target, { isCritical: false }); } // Status effects (only if target survived) if (target.hp > 0) { target.stunned = 1; // Can't act next turn // Push enemy 1 tile away (if this function exists) if (combatSystem.getPushDirection && combatSystem.pushUnit) { const pushDirection = combatSystem.getPushDirection(caster, target); if (pushDirection) { combatSystem.pushUnit(target, pushDirection); } } } combatSystem.showMessage(`${caster.name} shield bashes ${target.name}! Both take damage, target is stunned and pushed!`); // Check if anyone died if (target.hp <= 0) { combatSystem.processDeath(target, caster); } if (caster.hp <= 0) { combatSystem.processDeath(caster, target); } } }, "Counter": { name: "Counter", icon: "Bin/Contents/Combat/Skills/Basic/BA_Attack.png", type: "support", // Support since it's a defensive preparation description: "Prepare to counter the next melee attack. Only works against adjacent enemies.", cooldown: 3, range: 0, // Self-cast effect: function(caster, target, combatSystem) { // Set counter stance caster.counterStance = 1; // Lasts until next melee attack against this unit caster.counterReady = true; if (window.AudioEvents && typeof window.AudioEvents.emit === 'function') { window.AudioEvents.emit('combat:sfx', { id: 'combat_skill_inspire_mixed', volume: 0.55 }); } else if (window.AudioManager && typeof window.AudioManager.playSFX === 'function') { window.AudioManager.playSFX('combat_skill_inspire_mixed', 0.55); } combatSystem.showMessage(`${caster.name} prepares to counter the next melee attack!`); } }, "Poison Shot": { name: "Poison Shot", icon: "Bin/Contents/Combat/Skills/Icons/Skill_Poison_nb.png", type: "combat", description: "Arrow that deals damage immediately and for 4 turns.", cooldown: 4, range: 4, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target for poison shot!`); return; } // Immediate damage const immediateDamage = 8 + Math.floor(Math.random() * 3); target.hp = Math.max(0, target.hp - immediateDamage); if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('basicAttack', { caster: caster, target: target, damage: immediateDamage, isCritical: false }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(immediateDamage, target, { isCritical: false }); } // Set up turn counter for poison damage target.poisonTurnsLeft = 4; combatSystem.showMessage(`${caster.name} shoots a poison arrow at ${target.name} for ${immediateDamage} damage! Target will take poison damage for 4 turns!`); } }, "Bolster Defense": { name: "Bolster Defense", icon: "Bin/Contents/Combat/Skills/Icons/Skill_ShieldUp_nb.png", type: "support", description: "RISK: Knight can't attack next turn. CASCADE: Creates protective formation for allies.", cooldown: 4, range: 2, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs an ally to bolster!`); return; } // RISK: Sacrifice next attack caster.cantAttackNextTurn = true; // CASCADE: Create protective zone target.protectedBy = caster.id; target.defenseBonus = Math.floor((caster.defense || 1) * 0.5); target.formationDefense = 3; // Lasts 3 turns if (typeof combatSystem.addEffect === 'function') { combatSystem.addEffect(target, 'Bolster Defense', { type: "status", status: "bolster_guard", duration: 3, preventCatastrophic: true, stabilizedLimbs: ["torso", "leftArm", "rightArm", "head"], limbMitigation: { torso: 0.1, leftArm: 0.08, rightArm: 0.08, head: 0.06 } }); } // All allies adjacent to target get defense bonus const nearbyAllies = combatSystem.getAdjacentAllies(target); nearbyAllies.forEach(ally => { ally.formationDefense = 2; if (typeof combatSystem.addEffect === 'function') { combatSystem.addEffect(ally, 'Bolster Defense', { type: "status", status: "bolster_aura", duration: 2, stabilizedLimbs: ["torso", "leftArm", "rightArm"], limbMitigation: { torso: 0.06, leftArm: 0.05, rightArm: 0.05 } }); } }); if (window.AudioEvents && typeof window.AudioEvents.emit === 'function') { window.AudioEvents.emit('combat:sfx', { id: 'combat_skill_inspire_mixed', volume: 0.6 }); } else if (window.AudioManager && typeof window.AudioManager.playSFX === 'function') { window.AudioManager.playSFX('combat_skill_inspire_mixed', 0.6); } combatSystem.showMessage(`${caster.name} creates a defensive formation around ${target.name}! Knight can't attack next turn.`); } }, "Focused Strike": { name: "Focused Strike", icon: "Bin/Contents/Combat/Skills/infantry/I_HeavyAttack.png", type: "combat", description: "A high damage slash with a long cooldown.", cooldown: 2, range: 1, targetArea: ["torso", "head"], injuryImpact: { baseChance: 0.28, damageScaling: 0.52, severityThresholds: [ { minRatio: 0.36, severity: "moderate" }, { minRatio: 0.58, severity: "major" }, { minRatio: 0.7, severity: "critical" } ], catastrophicThreshold: "major", catastrophic: { baseDifficulty: 0.54, armorWeight: 0.02, preventBonus: 0.5, stabilizedBonus: 0.32, stabilizedFlagBonus: 0.24, onFailStatus: "missing", onFailSeverity: "permanent", onSaveStatus: "maimed", onSaveSeverity: "permanent", durations: { missing: 26, maimed: 18 } }, durations: { minor: 3, moderate: 4, major: 5, critical: 6, default: 4 }, durationVariance: 1, penalties: { minor: { defense_flat: -1 }, moderate: { defense_flat: -2, attack_flat: -1 }, major: { defense_flat: -2, attack_flat: -2, critChance_flat: -0.05 }, critical: { defense_flat: -3, attack_flat: -3, critChance_flat: -0.1 }, default: { defense_flat: -1 } } }, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target for focused strike!`); return; } // RISK: Can't move next turn (focusing requires staying in position) caster.cantMoveNextTurn = true; // REWARD + CASCADE: High damage with crit chance const baseDamage = Math.floor((caster.attack || 1) * 1.2); const isCrit = Math.random() < 0.4; // 40% crit chance const damage = isCrit ? Math.floor(baseDamage * 1.5) : baseDamage; target.hp = Math.max(0, target.hp - damage); if (isCrit) { if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('criticalHit', { caster: caster, target: target, damage: damage }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(damage, target, { isCritical: true }); } // CASCADE: Crit marks target for team focus fire target.marked = 2; combatSystem.showMessage(`${caster.name} lands a CRITICAL focused strike on ${target.name} for ${damage} damage! Target is marked for team focus!`); } else { if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('basicAttack', { caster: caster, target: target, damage: damage, isCritical: false }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(damage, target, { isCritical: false }); } combatSystem.showMessage(`${caster.name} focused strikes ${target.name} for ${damage} damage! ${caster.name} can't move next turn.`); } } }, /* === COMMANDER SKILLS === */ "Battle Cry": { name: "Battle Cry", icon: "Bin/Contents/Combat/Skills/Icons/Skill_BattleCry_nb.png", type: "support", description: "RISK: Commander becomes priority target. CASCADE: All allies get damage bonus and fearless.", cooldown: 5, range: 0, effect: function(caster, target, combatSystem) { // RISK: Draw enemy attention caster.priorityTarget = 2; // AI will prefer to attack Commander caster.vulnerable = 1; // CASCADE: Massive team buff const allies = combatSystem.getAllies(caster); allies.forEach(ally => { ally.inspired = 3; // +50% damage for 3 turns ally.fearless = 2; // Immune to debuffs for 2 turns ally.battleCryBonus = Math.floor((ally.attack || 1) * 0.5); }); if (window.AudioEvents && typeof window.AudioEvents.emit === 'function') { window.AudioEvents.emit('combat:sfx', { id: 'combat_skill_inspire_mixed', volume: 0.7 }); } else if (window.AudioManager && typeof window.AudioManager.playSFX === 'function') { window.AudioManager.playSFX('combat_skill_inspire_mixed', 0.7); } combatSystem.showMessage(`${caster.name} rallies the troops! All allies are inspired and fearless, but Commander is exposed!`); } }, "Inspire": { name: "Inspire", icon: "Bin/Contents/Combat/Skills/Icons/Skill_Commander_nb.png", type: "support", description: "RISK: Commander can't act next turn. CASCADE: Target ally gets immediate extra action.", cooldown: 4, range: 3, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs an ally to inspire!`); return; } // RISK: Lock down completely next turn caster.cantMoveNextTurn = true; caster.cantAttackNextTurn = true; // CASCADE: Ally gets to act again immediately target.bonusAction = true; target.canMove = true; target.canAttack = true; target.nextAttackGuaranteedCrit = true; target.inspired = 1; if (window.AudioEvents && typeof window.AudioEvents.emit === 'function') { window.AudioEvents.emit('combat:sfx', { id: 'combat_skill_inspire_mixed', volume: 0.65 }); } else if (window.AudioManager && typeof window.AudioManager.playSFX === 'function') { window.AudioManager.playSFX('combat_skill_inspire_mixed', 0.65); } combatSystem.showMessage(`${caster.name} inspires ${target.name}! Target can act again with guaranteed crit, but Commander is locked down!`); } }, /* === RANGER SKILLS === */ "Quick Shot": { name: "Quick Shot", icon: "Bin/Contents/Combat/Skills/Icons/Skill_ArcherShoot_nb.png", type: "combat", description: "Fast ranged attack that allows movement afterward.", cooldown: 0, range: 4, targeting: { type: "diagonal", sourceStat: "Dexterity", baseRange: 3, perStat: 5 }, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target for quick shot!`); return; } // REWARD: Can move after attacking const damage = Math.floor((caster.attack || 1) * 0.8); target.hp = Math.max(0, target.hp - damage); if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('basicAttack', { caster: caster, target: target, damage: damage, isCritical: false }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(damage, target, { isCritical: false }); } // CASCADE: Allows movement after attack caster.canMove = true; // Can move after this attack combatSystem.showMessage(`${caster.name} quick shots ${target.name} for ${damage} damage! Can still move this turn.`); } }, "Hamstring": { name: "Hamstring", icon: "Bin/Contents/Combat/Skills/Icons/Skill_ShotLeg_nb.png", type: "combat", description: "RISK: Must stay in range of angry enemy. CASCADE: Creates movement-denied zone.", cooldown: 4, range: 2, targetArea: ["leftLeg", "rightLeg"], injuryImpact: { baseChance: 0.37, damageScaling: 0.42, severityThresholds: [ { minRatio: 0.24, severity: "moderate" }, { minRatio: 0.44, severity: "major" }, { minRatio: 0.6, severity: "critical" } ], catastrophicThreshold: "major", catastrophic: { baseDifficulty: 0.48, armorWeight: 0.02, preventBonus: 0.52, stabilizedBonus: 0.32, stabilizedFlagBonus: 0.24, onFailStatus: "maimed", onFailSeverity: "permanent", onSaveStatus: "injured", onSaveSeverity: "major", durations: { maimed: 22 } }, durations: { minor: 2, moderate: 3, major: 4, critical: 5, default: 3 }, penalties: { minor: { movement_flat: -1 }, moderate: { movement_flat: -2 }, major: { movement_flat: -2, attack_flat: -1 }, critical: { movement_flat: -3, attack_flat: -2 }, default: { movement_flat: -1 } } }, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target to hamstring!`); return; } // RISK: Target will prioritize Ranger target.vendetta = caster.id; // Will focus on Ranger if able // CASCADE: Area denial const damage = Math.floor((caster.attack || 1) * 0.9); target.hp = Math.max(0, target.hp - damage); target.movementDisabled = 2; // Can't move for 2 turns if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('basicAttack', { caster: caster, target: target, damage: damage, isCritical: false }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(damage, target, { isCritical: false }); } // Create caltrops around target const adjacentTiles = combatSystem.getAdjacentTiles(target); adjacentTiles.forEach(tile => { combatSystem.setTileStatus(tile.x, tile.y, 'caltrops', 3); }); combatSystem.showMessage(`${caster.name} hamstrings ${target.name} and scatters caltrops! Target can't move and will focus on Ranger!`); } }, "Endure": { name: "Endure", icon: "Bin/Contents/Combat/Skills/Icons/Skill_Resilience_nb.png", type: "support", description: "RISK: Becomes immobile. CASCADE: Damage resistance stacks for nearby allies.", cooldown: 5, range: 0, effect: function(caster, target, combatSystem) { // RISK: Can't move while enduring caster.cantMoveNextTurn = true; caster.immobile = 1; // Can't move next turn // REWARD + CASCADE: Strong resistance that spreads caster.damageResistance = 3; // Takes 50% less damage for 3 turns caster.enduring = 3; // CASCADE: Nearby allies get inspiration from endurance const nearbyAllies = combatSystem.getNearbyAllies(caster, 2); nearbyAllies.forEach(ally => { ally.inspirationFromEndurance = 2; // +25% damage for 2 turns ally.damageResistance = 1; // Minor resistance }); if (window.AudioEvents && typeof window.AudioEvents.emit === 'function') { window.AudioEvents.emit('combat:sfx', { id: 'combat_skill_inspire_mixed', volume: 0.6 }); } else if (window.AudioManager && typeof window.AudioManager.playSFX === 'function') { window.AudioManager.playSFX('combat_skill_inspire_mixed', 0.6); } combatSystem.showMessage(`${caster.name} endures! Strong damage resistance and inspires nearby allies, but becomes immobile!`); } }, /* === GUARDIAN SKILLS === */ "Shield Wall": { name: "Shield Wall", icon: "Bin/Contents/Combat/Skills/Icons/Skill_ShieldWall_nb.png", type: "support", description: "RISK: Guardian takes all damage meant for allies. CASCADE: Creates safe positioning zone.", cooldown: 4, range: 1, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs an ally to protect!`); return; } // RISK: Redirect damage to self caster.redirectDamage = 3; // All ally damage goes to Guardian for 3 turns caster.shieldWallActive = 3; // CASCADE: Create tactical advantages target.protectedByWall = 3; target.canAttackSafely = true; const protectedAllies = combatSystem.getAlliesInRange(caster, 2); protectedAllies.forEach(ally => { ally.behindShieldWall = 2; ally.attackBonus = Math.floor((ally.attack || 1) * 0.2); // +20% attack while protected }); if (window.AudioEvents && typeof window.AudioEvents.emit === 'function') { window.AudioEvents.emit('combat:sfx', { id: 'combat_skill_inspire_mixed', volume: 0.6 }); } else if (window.AudioManager && typeof window.AudioManager.playSFX === 'function') { window.AudioManager.playSFX('combat_skill_inspire_mixed', 0.6); } combatSystem.showMessage(`${caster.name} creates a shield wall protecting ${target.name}! Allies gain attack bonus but Guardian will take their damage!`); } }, /* === ASSASSIN SKILLS === */ "Crippling Strike": { name: "Crippling Strike", icon: "Bin/Contents/Combat/Skills/Icons/Skill_Backstab_nb.png", type: "combat", description: "RISK: Teleport into danger. CASCADE: Creates marked target for team focus fire.", cooldown: 3, range: 2, targetArea: ["leftLeg", "rightLeg", "torso"], injuryImpact: { baseChance: 0.4, damageScaling: 0.48, severityThresholds: [ { minRatio: 0.28, severity: "moderate" }, { minRatio: 0.48, severity: "major" }, { minRatio: 0.64, severity: "critical" } ], catastrophicThreshold: "major", catastrophic: { baseDifficulty: 0.5, armorWeight: 0.02, preventBonus: 0.5, stabilizedBonus: 0.32, stabilizedFlagBonus: 0.24, onFailStatus: "maimed", onFailSeverity: "permanent", onSaveStatus: "injured", onSaveSeverity: "major", durations: { maimed: 24 } }, durations: { minor: 3, moderate: 4, major: 5, critical: 6, default: 4 }, penalties: { minor: { movement_flat: -1 }, moderate: { movement_flat: -2, attack_flat: -1 }, major: { movement_flat: -3, attack_flat: -2 }, critical: { movement_flat: -3, attack_flat: -3, critChance_flat: -0.1 }, default: { movement_flat: -1 } } }, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target for crippling strike!`); return; } // RISK: Teleport behind enemy (potentially surrounded) const behindPosition = combatSystem.getPositionBehind(target, caster); if (behindPosition) { combatSystem.moveUnit(caster, behindPosition.x, behindPosition.y); caster.exposedPosition = true; // Vulnerable to surrounding enemies } // REWARD + CASCADE: High damage + mark for team const damage = Math.floor((caster.attack || 1) * 1.3); target.hp = Math.max(0, target.hp - damage); if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('basicAttack', { caster: caster, target: target, damage: damage, isCritical: false }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(damage, target, { isCritical: false }); } // Status effects (only if target survived) if (target.hp > 0) { target.marked = 3; // CASCADE: All allies get +50% damage vs marked enemies target.crippled = 2; // Reduced attack for 2 turns } combatSystem.showMessage(`${caster.name} teleports behind ${target.name} for ${damage} damage! Target is marked and crippled!`); } }, /* === BERSERKER SKILLS === */ "Last Stand": { name: "Last Stand", icon: "Bin/Contents/Combat/Skills/Icons/Skill_Frenzy_nb.png", type: "support", description: "RISK: Take damage over time. CASCADE: Rage empowers nearby allies.", cooldown: 6, range: 0, effect: function(caster, target, combatSystem) { // RISK: Berserker takes DoT caster.berserkerRage = 4; // Takes damage each turn but... caster.rageDamagePerTurn = Math.floor((caster.maxHp || 10) * 0.1); caster.lastStandActive = true; // CASCADE: Rage inspires allies const nearbyAllies = combatSystem.getNearbyAllies(caster, 2); nearbyAllies.forEach(ally => { ally.bloodlust = 3; // Damage heals them for 3 turns ally.rageBonus = Math.floor((ally.attack || 1) * 0.4); // +40% attack }); // REWARD: Berserker gets massive bonuses but takes damage caster.attackMultiplier = 1.8; // +80% damage caster.critChance = 0.5; // 50% crit chance if (window.AudioEvents && typeof window.AudioEvents.emit === 'function') { window.AudioEvents.emit('combat:sfx', { id: 'combat_skill_inspire_mixed', volume: 0.65 }); } else if (window.AudioManager && typeof window.AudioManager.playSFX === 'function') { window.AudioManager.playSFX('combat_skill_inspire_mixed', 0.65); } combatSystem.showMessage(`${caster.name} enters berserker rage! Takes damage over time but empowers nearby allies and becomes devastating!`); } }, /* === SKIRMISHER SKILLS === */ "Rapid Fire": { name: "Rapid Fire", icon: "Bin/Contents/Combat/Skills/Icons/Skill_MultiArrowShoot_nb.png", type: "combat", description: "RISK: Reduced accuracy. CASCADE: Multiple shots can trigger ally follow-ups.", cooldown: 4, range: 4, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target for rapid fire!`); return; } // RISK: Each shot has chance to miss let totalDamage = 0; let hitCount = 0; let hitsArray = []; for (let i = 0; i < 3; i++) { const didHit = Math.random() < 0.7; // 70% hit chance per shot const damage = didHit ? Math.floor((caster.attack || 1) * 0.6) : 0; hitsArray.push({ damage: damage, hit: didHit }); if (didHit) { totalDamage += damage; hitCount++; } } target.hp = Math.max(0, target.hp - totalDamage); if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('multiHit', { caster: caster, target: target, hits: hitsArray }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(totalDamage, target, { isCritical: false }); } // CASCADE: Multiple hits create opportunities if (hitCount >= 2) { target.suppressed = 2; // Can't move for 2 turns // Nearby allies get opportunity attacks const allies = combatSystem.getNearbyAllies(target, 2); allies.forEach(ally => { ally.opportunityAttack = 1; // Can make free attack }); } combatSystem.showMessage(`${caster.name} rapid fires at ${target.name}! ${hitCount}/3 shots hit for ${totalDamage} total damage!`); } }, /* === MEDIC SKILLS === */ "First Aid": { name: "First Aid", icon: "Bin/Contents/Combat/Skills/Icons/Skill_Bandages_nb.png", type: "support", description: "RISK: Medic becomes vulnerable while healing. CASCADE: Healed ally gets combat bonuses.", cooldown: 3, range: 2, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs an ally to heal!`); return; } // RISK: Healing leaves Medic exposed caster.vulnerable = 2; // Takes extra damage for 2 turns caster.cannotMove = 1; // Can't move next turn // REWARD + CASCADE: Strong heal + combat boost const healAmount = Math.floor((target.maxHp || 10) * 0.5); target.hp = Math.min(target.maxHp || 10, target.hp + healAmount); target.combatStims = 2; // +damage and +crit for 2 turns target.painResistance = 1; // Ignores next damage source // Remove one debuff if (target.stunned > 0) target.stunned = 0; if (target.marked > 0) target.marked = 0; if (target.exposed > 0) target.exposed = 0; if (typeof combatSystem.addEffect === 'function') { combatSystem.addEffect(target, 'First Aid', { type: "status", status: "first_aid_stabilized", duration: 3, preventCatastrophic: true, stabilizedLimbs: ["head", "torso", "leftArm", "rightArm", "leftLeg", "rightLeg"], limbMitigation: { head: 0.08, torso: 0.12, leftArm: 0.1, rightArm: 0.1, leftLeg: 0.1, rightLeg: 0.1 } }); } if (window.AudioEvents && typeof window.AudioEvents.emit === 'function') { window.AudioEvents.emit('combat:sfx', { id: 'heal', volume: 0.65 }); } else if (window.AudioManager && typeof window.AudioManager.playSFX === 'function') { window.AudioManager.playSFX('heal', 0.65); } combatSystem.showMessage(`${caster.name} heals ${target.name} for ${healAmount} HP! Target gets combat stims and pain resistance, but Medic is vulnerable!`); } }, /* === MARKSMAN SKILLS === */ "Aimed Shot": { name: "Aimed Shot", icon: "Bin/Contents/Combat/Skills/Icons/Skill_FocusArrow_nb.png", type: "combat", description: "A devastating shot that deals double damage. Long cooldown.", cooldown: 4, range: 6, targetArea: ["torso", "head"], injuryImpact: { baseChance: 0.2, damageScaling: 0.5, severityThresholds: [ { minRatio: 0.4, severity: "moderate" }, { minRatio: 0.65, severity: "major" } ], durations: { minor: 2, moderate: 3, major: 4, critical: 5, default: 3 }, penalties: { minor: { defense_flat: -1 }, moderate: { defense_flat: -2, attack_flat: -1 }, major: { defense_flat: -3, attack_flat: -2 }, critical: { defense_flat: -4, attack_flat: -3 }, default: { defense_flat: -1 } } }, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target for aimed shot!`); return; } // Deal double damage - twice the normal attack const normalAttack = caster.attack || 1; const aimedDamage = normalAttack * 2; target.hp = Math.max(0, target.hp - aimedDamage); if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('basicAttack', { caster: caster, target: target, damage: aimedDamage, isCritical: false }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(aimedDamage, target, { isCritical: false }); } combatSystem.showMessage(`${caster.name} takes careful aim and deals ${aimedDamage} damage to ${target.name}!`); // Check if target died if (target.hp <= 0) { combatSystem.processDeath(target, caster); } } }, "Precision Shot": { name: "Precision Shot", icon: "Bin/Contents/Combat/Skills/Icons/Skill_PiercingAttack_nb.png", type: "combat", description: "A precise shot that cripples enemy movement. Target cannot move but can still attack.", cooldown: 3, range: 5, requiresSkillTags: ["requiresDominantHand", "ranged"], requiresLimbTags: ["grasp", "senses"], limbUsage: { dominant: 1 }, targetArea: ["leftLeg", "rightLeg"], injuryImpact: { baseChance: 0.36, damageScaling: 0.38, severityThresholds: [ { minRatio: 0.22, severity: "moderate" }, { minRatio: 0.38, severity: "major" }, { minRatio: 0.55, severity: "critical" } ], catastrophicThreshold: "major", catastrophic: { baseDifficulty: 0.48, armorWeight: 0.022, preventBonus: 0.52, stabilizedBonus: 0.32, stabilizedFlagBonus: 0.24, onFailStatus: "maimed", onFailSeverity: "permanent", onSaveStatus: "injured", onSaveSeverity: "major", durations: { maimed: 22 } }, durations: { minor: 2, moderate: 3, major: 4, critical: 5, default: 3 }, penalties: { minor: { movement_flat: -1 }, moderate: { movement_flat: -2 }, major: { movement_flat: -2, attack_flat: -1 }, critical: { movement_flat: -3, attack_flat: -2 }, default: { movement_flat: -1 } } }, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target for precision shot!`); return; } // Normal damage const damage = caster.attack || 1; target.hp = Math.max(0, target.hp - damage); if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('basicAttack', { caster: caster, target: target, damage: damage, isCritical: false }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); combatSystem.animationManager.playAnimation(target, 'Damaged', false); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { combatSystem.displayDamageNumber(damage, target, { isCritical: false }); } // *** FIXED: Use ONLY movementDisabled, remove canMove setting *** target.movementDisabled = 2; // Cannot move for 2 turns // Remove this line: target.canMove = false; console.log(`Applied movementDisabled to ${target.name}, movementDisabled = ${target.movementDisabled}`); combatSystem.showMessage(`${caster.name} precisely shots ${target.name} for ${damage} damage! Target is crippled and cannot move!`); // Check if target died if (target.hp <= 0) { combatSystem.processDeath(target, caster); } } }, /* === HORSEMEN SKILLS === */ "Charge": { name: "Charge", icon: "Bin/Contents/Combat/Skills/Icons/Skill_Charge_nb.png", type: "combat", description: "RISK: Must move in straight line through enemies. CASCADE: Tramples multiple foes.", cooldown: 3, range: 0, // Special movement-based range targetArea: ["torso", "leftLeg", "rightLeg"], injuryImpact: { baseChance: 0.26, damageScaling: 0.45, severityThresholds: [ { minRatio: 0.3, severity: "moderate" }, { minRatio: 0.5, severity: "major" }, { minRatio: 0.66, severity: "critical" } ], catastrophicThreshold: "major", catastrophic: { baseDifficulty: 0.53, armorWeight: 0.022, preventBonus: 0.45, stabilizedBonus: 0.3, stabilizedFlagBonus: 0.22, onFailStatus: "missing", onFailSeverity: "permanent", onSaveStatus: "maimed", onSaveSeverity: "permanent", durations: { missing: 28, maimed: 20 } }, durations: { minor: 2, moderate: 3, major: 4, critical: 5, default: 3 }, penalties: { minor: { movement_flat: -1 }, moderate: { movement_flat: -2, defense_flat: -1 }, major: { movement_flat: -2, defense_flat: -2 }, critical: { movement_flat: -3, defense_flat: -3 }, default: { movement_flat: -1 } } }, effect: function(caster, target, combatSystem) { if (!target) { combatSystem.showMessage(`${caster.name} needs a target to charge!`); return; } // Calculate charge path const dx = target.x - caster.x; const dy = target.y - caster.y; const distance = Math.abs(dx) + Math.abs(dy); if (distance > 3) { combatSystem.showMessage(`Target too far for charge! Maximum range 3.`); return; } // RISK: Must charge in straight line, can't stop let chargeX = caster.x; let chargeY = caster.y; let totalDamage = 0; let unitsHit = []; let targetsHitData = []; // Move step by step towards target const stepX = dx === 0 ? 0 : (dx > 0 ? 1 : -1); const stepY = dy === 0 ? 0 : (dy > 0 ? 1 : -1); for (let i = 0; i < distance; i++) { chargeX += stepX; chargeY += stepY; const unitInPath = combatSystem.getUnitAt(chargeX, chargeY); if (unitInPath && unitInPath.team !== caster.team) { // CASCADE: Trample damage const trampleDamage = Math.floor((caster.attack || 1) * (0.8 + (i * 0.2))); // More damage with momentum unitInPath.hp = Math.max(0, unitInPath.hp - trampleDamage); totalDamage += trampleDamage; unitsHit.push(unitInPath.name); targetsHitData.push({ target: unitInPath, damage: trampleDamage }); // Knockback effect const knockDirection = { dx: stepX, dy: stepY }; combatSystem.pushUnit(unitInPath, knockDirection); } } if (window.CombatAnimationSequence && typeof window.CombatAnimationSequence.play === 'function') { window.CombatAnimationSequence.play('charge', { caster: caster, targetsHit: targetsHitData, totalDamage: totalDamage }); } else if (combatSystem && combatSystem.animationManager) { combatSystem.animationManager.playAnimation(caster, 'Strike', false); targetsHitData.forEach(hit => { if (hit.target) { combatSystem.animationManager.playAnimation(hit.target, 'Damaged', false); } }); } else if (combatSystem && typeof combatSystem.displayDamageNumber === 'function') { targetsHitData.forEach(hit => { if (hit.target && hit.damage) { combatSystem.displayDamageNumber(hit.damage, hit.target, { isCritical: false }); } }); } // Move Horsemen to final position (past target if possible) const finalX = Math.min(combatSystem.boardWidth - 1, Math.max(0, chargeX + stepX)); const finalY = Math.min(combatSystem.boardHeight - 1, Math.max(0, chargeY + stepY)); combatSystem.moveUnit(caster, finalX, finalY); // RISK: Horsemen is now potentially isolated caster.chargeExhaustion = 1; // Reduced defense next turn if (unitsHit.length > 0) { combatSystem.showMessage(`${caster.name} charges through ${unitsHit.join(', ')} for ${totalDamage} total trample damage!`); } else { combatSystem.showMessage(`${caster.name} charges forward but hits no enemies!`); } } } }>> <<run setup.titleAbilitySets = { "Lord": { majorOptions: ["Shield Bash", "Battle Cry", "Bolster Defense"], major: "Shield Bash", archetype: "Shield Bash", combat: ["Shield Bash", "Battle Cry", "Bolster Defense", "Inspire", "Heavy Strike", "", ""], support: ["Defensive Stance", "Quickstep", "", ""] }, "Knight": { majorOptions: ["Focused Strike", "Shield Bash", "Counter"], major: "Focused Strike", combat: ["Heavy Strike", "Shield Break", "Shield Bash", "Bolster Defense", "Focused Strike", "", ""], support: ["Defensive Stance", "", "", ""] }, "Guardian": { majorOptions: ["Shield Wall", "Bolster Defense", "Defensive Stance"], major: "Shield Wall", combat: ["Heavy Strike", "Shield Break", "Shield Wall", "Shield Bash", "Endure", "", ""], support: ["Defensive Stance", "Bolster Defense", "", ""] }, "Assassin": { majorOptions: ["Crippling Strike", "Hamstring", "Throwing Knife"], major: "Crippling Strike", combat: ["Heavy Strike", "Throwing Knife", "Crippling Strike", "Hamstring", "Focused Strike", "", ""], support: ["Defensive Stance", "Quickstep", "", ""] }, "Berserker": { majorOptions: ["Last Stand", "Battle Cry", "Heavy Strike"], major: "Last Stand", combat: ["Heavy Strike", "Shield Break", "Focused Strike", "Battle Cry", "Last Stand", "", ""], support: ["Defensive Stance", "Quickstep", "", ""] }, "Horsemen": { majorOptions: ["Charge", "Hamstring", "Battle Cry"], major: "Charge", combat: ["Heavy Strike", "Shield Break", "Focused Strike", "Hamstring", "Charge", "", ""], support: ["Defensive Stance", "Quickstep", "", ""] }, "Ranger": { majorOptions: ["Aimed Shot", "Precision Shot", "Poison Shot"], major: "Aimed Shot", combat: ["Heavy Strike", "Quickstep", "Quick Shot", "Hamstring", "Endure", "", ""], support: ["Defensive Stance", "Quickstep", "", ""] }, "Skirmisher": { majorOptions: ["Quick Shot", "Rapid Fire", "Hamstring"], major: "Quick Shot", combat: ["Heavy Strike", "Quickstep", "Quick Shot", "Hamstring", "Rapid Fire", "", ""], support: ["Defensive Stance", "Quickstep", "", ""] }, "Marksman": { majorOptions: ["Aimed Shot", "Precision Shot", "Poison Shot"], major: "Aimed Shot", combat: ["Heavy Strike", "Quickstep", "Focused Strike", "Quick Shot", "Precision Shot", "", ""], support: ["Defensive Stance", "Quickstep", "", ""] }, "Commander": { majorOptions: ["Battle Cry", "Inspire", "Bolster Defense"], major: "Battle Cry", combat: ["Heavy Strike", "Shield Break", "Battle Cry", "Bolster Defense", "Inspire", "", ""], support: ["Defensive Stance", "Quickstep", "", ""] }, "Medic": { majorOptions: ["First Aid", "Bolster Defense", "Defensive Stance"], major: "First Aid", combat: ["Heavy Strike", "Shield Break", "First Aid", "Bolster Defense", "Endure", "", ""], support: ["Defensive Stance", "Bolster Defense", "", ""] } }>> <<run window.titleAbilitySets = setup.titleAbilitySets>>\r\n /* Combat */ <<set $combat to { "health": 100, "maxHealth": 100, "armor": 0, "damage": 0, "criticalChance": 0, "dodgeChance": 0 }>> /* Special Skills (Player Character) */ <<set $combatSkills to { "selected": "", "available": { /* AVAILABLE SKILLS NOW DEFINED IN setup.combatSkillDefinitions */ }, "cooldowns": { /* Player cooldowns still potentially tracked here if needed */ "Inspire": 0, "Last Stand": 0, "Rapid Fire": 0 } }>> <<set $isCompanionOnlyBattle = true>> <<set $companionCooldowns = {}>>
<<set $currentEncounterData = null>> <<run setup.presetEncounters = { "tutorial_fight": { id: "tutorial_fight", companionOnly: true, requirements: { minInfluence: 0, maxInfluence: 999, canRepeat: false }, influenceReward: 3, respawnDays: 0, background: "Bin/Contents/Locations/Greenwood_Forest/Combat/Southwood_Forest_Hideout_Day.png", enemies: [ { personaId: "garron_pitchfire", x: 7, y: 2 } ], lootTable: { gold: { min: 20, max: 35 }, resources: { food: { min: 10, max: 18 }, population: { min: 5, max: 10 } }, capturedSlaves: { male: { min: 0, max: 1, chance: 0.4 }, female: { min: 0, max: 1, chance: 0.15 } }, guaranteed: [ { itemId: "chain_links", quantity: 1 } ], pools: [ { poolId: "bandit_common", rolls: 1 } ] }, timeSkipMinutes: 240, title: "Training Yard Skirmish", description: "Captain Garron walks you through formation basics while Pitchfire watches for the tiniest misstep.", postBattleNarrative: { victory: { overwhelming: "The training yard falls silent as Garron nods in approval. You dismantled the formation with confidence and leave with a firm grip on the basics.", standard: "You edge out the skirmish. Garron calls the exercise a success, but points out the gaps in your line.", barely: "You barely hold the line and win by inches. The yard falls quiet as you catch your breath, shaken but wiser." }, defeat: "The drill goes poorly. Garron calls a halt before it turns ugly, and you retreat to regroup." } }, "bandit_duo_fight": { id: "bandit_duo_fight", companionOnly: true, requirements: { minInfluence: 0, maxInfluence: 50, canRepeat: true }, influenceReward: 5, respawnDays: 2, background: "Bin/Contents/Locations/Greenwood_Forest/Combat/Southwood_Forest_Hideout_Day.png", enemies: [ { personaId: "brunn_ironskull", x: 6, y: 2 }, { personaId: "anise_quickblade", x: 8, y: 1 } ], lootTable: { gold: { min: 30, max: 55 }, resources: { food: { min: 12, max: 24 }, population: { min: 8, max: 16 } }, capturedSlaves: { male: { min: 0, max: 2, chance: 0.55 }, female: { min: 0, max: 1, chance: 0.25 } }, guaranteed: [ { itemId: "chain_links", quantity: 1 } ], pools: [ { poolId: "bandit_common", rolls: 2 }, { poolId: "bandit_rare", rolls: 1 } ] }, timeSkipMinutes: 240, title: "Bandit Duo Camp", description: "Brunn Ironskull and Anise Quickblade celebrate a fresh haul by the fire, their guard down and laughter echoing through the trees.", postBattleNarrative: { victory: { overwhelming: "Brunn and Anise crumple fast, their camp yours before the fire can die down. Their swagger evaporates.", standard: "You rout the duo after a sharp exchange. The camp is yours, but the pair make you work for it.", barely: "You scrape out the win against the duo, bruised and wary. The camp is yours, but the cost is written in every ache." }, defeat: "The duo outmaneuver you and fade into the trees with your blood on their blades." }, monthlyRoutine: { week1: { Moonday: { Morning: { description: "Brunn snores loudly against a tree, ale bottle still in hand. Anise sits nearby nursing a pounding headache, barely aware of her surroundings.", enemyState: "hungover", atmosphere: "Empty bottles and food scraps litter the campsite." }, Afternoon: { description: "Both bandits are awake but moving slowly. Brunn counts coins while Anise sharpens her blade with shaky hands.", enemyState: "recovering", atmosphere: "The midday sun beats down mercilessly on their throbbing heads." }, Evening: { description: "Spirits recovering, the duo starts cooking a fresh meal. Brunn boasts about their weekend raid while Anise smirks knowingly.", enemyState: "relaxed", atmosphere: "The smell of roasting meat fills the forest air." }, Night: { description: "Full bellies and recovered spirits. They sit by the fire passing a wineskin, but keeping weapons close this time.", enemyState: "content", atmosphere: "Firelight dances across their faces as crickets chirp." } }, Fireday: { Morning: { description: "Brunn practices axe swings against a training dummy. Anise scouts the perimeter, eyes sharp and focused.", enemyState: "training", atmosphere: "Morning mist still clings to the forest floor." }, Afternoon: { description: "The duo argues over the best route for their next raid. Maps and crude drawings are spread across a log.", enemyState: "planning", atmosphere: "Tension hangs in the air as they disagree on tactics." }, Evening: { description: "Agreement reached, they celebrate with dice games. Gold coins clink as they bet on each throw.", enemyState: "gambling", atmosphere: "Laughter and curses echo through the trees." }, Night: { description: "Both are deep in their cups, celebrating tomorrow's plan. Anise has won most of Brunn's coin, much to his grumbling.", enemyState: "drunk", atmosphere: "The campfire burns low as they toast their future success." } }, Waterday: { Morning: { description: "The camp is empty save for cold ashes. Fresh tracks lead north toward the trade road.", enemyState: "absent", atmosphere: "Only birds disturb the silence of the abandoned camp." }, Afternoon: { description: "Still no sign of the bandits. Their supplies remain, suggesting they plan to return.", enemyState: "absent", atmosphere: "The wind rustles through their tent flaps." }, Evening: { description: "Brunn and Anise return dragging heavy sacks. Both are wounded and bleeding, but grinning wildly at their success.", enemyState: "wounded_triumphant", atmosphere: "Blood trails mark their path back to camp." }, Night: { description: "They tend their wounds by firelight, counting their stolen gold. Anise binds Brunn's arm while he drinks to dull the pain.", enemyState: "injured", atmosphere: "Bandages and bloodstained cloth litter the ground." } }, Windday: { Morning: { description: "Brunn's arm is bandaged but he moves stiffly. Anise limps slightly but maintains watch, paranoid about pursuit.", enemyState: "injured_alert", atmosphere: "Every snapping twig makes them reach for weapons." }, Afternoon: { description: "Anise frantically buries their stolen goods while Brunn keeps nervous watch. They're clearly spooked.", enemyState: "paranoid", atmosphere: "Sweat beads on their foreheads despite the cool air." }, Evening: { description: "No campfire tonight. Both sit in darkness, weapons drawn, listening to every sound in the forest.", enemyState: "hiding", atmosphere: "Darkness conceals them, but also amplifies their fear." }, Night: { description: "One sleeps fitfully while the other keeps watch. They take turns throughout the night, too afraid to both sleep.", enemyState: "sleep_rotation", atmosphere: "Moonlight filters through the canopy above." } }, Earthday: { Morning: { description: "Exhausted from a sleepless night, both bandits sit bleary-eyed around a small fire, constantly scanning the forest.", enemyState: "exhausted_alert", atmosphere: "Dark circles under their eyes tell of their sleepless vigil." }, Afternoon: { description: "Anise sleeps while Brunn keeps watch, his head nodding dangerously low before jerking back up.", enemyState: "half_asleep", atmosphere: "The camp is eerily quiet save for Anise's soft snoring." }, Evening: { description: "Brunn sleeps now while Anise sits watch, though her eyelids droop heavily.", enemyState: "half_asleep", atmosphere: "The setting sun paints the camp in orange and gold." }, Night: { description: "Both have finally collapsed in exhaustion. They sleep back-to-back, weapons in hand, but neither stirs.", enemyState: "sleeping_exhausted", atmosphere: "Complete silence except for their heavy breathing." } }, Sunday: { Morning: { description: "They wake slowly, stretching sore muscles. Relief washes over them as they realize no one came during the night.", enemyState: "relieved", atmosphere: "Morning birdsong seems sweeter after days of paranoia." }, Afternoon: { description: "The duo digs up their buried loot, dividing it into piles. Arguments break out over the split.", enemyState: "arguing", atmosphere: "Raised voices carry through the forest." }, Evening: { description: "Despite their argument, they've started a victory celebration. Fresh ale flows as they toast their successful raid.", enemyState: "celebrating", atmosphere: "Music from a battered lute fills the air." }, Night: { description: "Completely drunk and celebrating, they've let their guard down entirely. Both laugh and sing loudly.", enemyState: "very_drunk", atmosphere: "Their raucous celebration can be heard from far away." } } }, week2: { Moonday: { Morning: { description: "Another morning of hangovers. Brunn vomits behind a tree while Anise clutches her head, swearing off drinking forever.", enemyState: "severely_hungover", atmosphere: "The stench of vomit and stale ale permeates the camp." }, Afternoon: { description: "Both sit in miserable silence, drinking water and eating stale bread. Neither has the energy for conversation.", enemyState: "miserable", atmosphere: "Flies buzz around the dirty dishes from last night's feast." }, Evening: { description: "Feeling slightly better, they begin cleaning the disaster of their camp. Anise makes weak jokes about never drinking again.", enemyState: "recovering", atmosphere: "The camp slowly transforms from pigsty to livable." }, Night: { description: "They eat a quiet dinner, both too tired and sick to celebrate. They turn in early, craving real rest.", enemyState: "tired", atmosphere: "The campfire burns low as they settle in for sleep." } }, Fireday: { Morning: { description: "Anise is gone, likely hunting. Brunn sits alone maintaining their weapons and armor with methodical focus.", enemyState: "alone_focused", atmosphere: "The rhythmic scrape of whetstone on steel fills the morning air." }, Afternoon: { description: "Anise returns with two rabbits. The duo works together skinning and preparing the meat in comfortable silence.", enemyState: "domestic", atmosphere: "Blood and fur mark their workspace as they efficiently butcher the game." }, Evening: { description: "They enjoy a proper meal together, roasted rabbit and foraged greens. The mood is almost peaceful.", enemyState: "content", atmosphere: "The smell of well-cooked food is a welcome change from dried rations." }, Night: { description: "Brunn tells old war stories while Anise listens, occasionally interjecting with her own tales. Weapons rest nearby but forgotten.", enemyState: "relaxed", atmosphere: "Nostalgia and warmth fill the night air along with woodsmoke." } }, Waterday: { Morning: { description: "A merchant's wagon was spotted yesterday. Both bandits prepare their gear with professional efficiency.", enemyState: "preparing", atmosphere: "The air is tense with pre-raid focus." }, Afternoon: { description: "Weapons sharpened and supplies packed, they discuss the plan one final time. Hand signals are reviewed.", enemyState: "planning_raid", atmosphere: "Every movement is deliberate, every word carefully considered." }, Evening: { description: "They wait in ambush position near the trade road. Both are completely still, eyes fixed on the path.", enemyState: "ambush_position", atmosphere: "The forest holds its breath along with them." }, Night: { description: "Back at camp, furious and empty-handed. The wagon never came. Anise throws her knife into a tree repeatedly in frustration.", enemyState: "frustrated", atmosphere: "Curses and the thunk of metal hitting wood punctuate the night." } }, Windday: { Morning: { description: "Brunn suggests checking their information source. Anise angrily insists the snitch must have betrayed them.", enemyState: "suspicious", atmosphere: "Trust between them seems to fray at the edges." }, Afternoon: { description: "They've agreed to investigate. The camp is locked down and hidden with branches and leaves. Both are packing light.", enemyState: "departing", atmosphere: "The camp disappears into the forest as if it never existed." }, Evening: { description: "The camp remains abandoned, all signs of habitation carefully concealed.", enemyState: "absent", atmosphere: "Only a trained eye could spot the hidden camp." }, Night: { description: "Still no sign of the bandits. The hidden camp remains undisturbed.", enemyState: "absent", atmosphere: "Forest creatures have begun to reclaim the space." } }, Earthday: { Morning: { description: "The camp is still abandoned.", enemyState: "absent", atmosphere: "Dew covers the concealed supplies." }, Afternoon: { description: "The camp remains empty and hidden.", enemyState: "absent", atmosphere: "A deer passes through, unaware of the hidden camp nearby." }, Evening: { description: "Brunn and Anise return, looking grim and bloodied. They immediately begin breaking camp with urgent efficiency.", enemyState: "fleeing", atmosphere: "Fear drives their hasty movements." }, Night: { description: "They've established a new temporary camp further into the forest. Both are wounded and watching the shadows with wide eyes.", enemyState: "wounded_hiding", atmosphere: "Every sound sends their hands to their weapons." } }, Sunday: { Morning: { description: "In their new hiding spot, Anise tends Brunn's serious shoulder wound. He grits his teeth against the pain.", enemyState: "seriously_wounded", atmosphere: "The smell of infection threatens despite her best efforts." }, Afternoon: { description: "Brunn lies feverish while Anise forages desperately for healing herbs. She glances back at him constantly, worry clear on her face.", enemyState: "critical", atmosphere: "Brunn's labored breathing is the only sound in the hidden camp." }, Evening: { description: "Anise has found some herbs and forced them down Brunn's throat. His fever breaks slightly. She sits exhausted beside him.", enemyState: "stabilizing", atmosphere: "Relief mingles with lingering fear in the quiet camp." }, Night: { description: "Brunn sleeps fitfully but more peacefully. Anise maintains vigilant watch, determined not to be caught vulnerable again.", enemyState: "guarded_recovery", atmosphere: "Moonlight filters through their hastily built shelter." } } } } }, "bandit_pack_ambush": { id: "bandit_pack_ambush", companionOnly: true, requirements: { minInfluence: 30, maxInfluence: 999, canRepeat: true }, influenceReward: 10, respawnDays: 3, background: "Bin/Contents/Locations/Greenwood_Forest/Combat/Southwood_Forest_Hideout_Day.png", enemies: [ { personaId: "joric_steelgut", x: 7, y: 2 }, { personaId: "lysa_daggersong", x: 6, y: 1 }, { personaId: "mira_cutlass", x: 8, y: 3 } ], lootTable: { gold: { min: 45, max: 80 }, resources: { food: { min: 18, max: 32 }, population: { min: 10, max: 22 } }, capturedSlaves: { male: { min: 1, max: 3, chance: 0.7 }, female: { min: 0, max: 2, chance: 0.35 } }, guaranteed: [ { itemId: "chain_links", quantity: 2 } ], pools: [ { poolId: "bandit_common", rolls: 3 }, { poolId: "bandit_rare", rolls: 1 } ] }, timeSkipMinutes: 240, title: "Bandit Pack Ambush", description: "A trio of raiders lie in wait along the ridge, blades ready to spring the trap on passing caravans.", postBattleNarrative: { victory: { overwhelming: "The ambush collapses under your counterstrike. The trio never finds its rhythm, and the ridge is yours.", standard: "You break the pack after a hard fight. The path is safe again, for now.", barely: "You survive the ambush by the thinnest margin. The ridge is yours, but your line is ragged." }, defeat: "You are driven from the ridge and the raiders melt back into the hills." } }, "castle_gate_skirmish": { id: "castle_gate_skirmish", companionOnly: false, musicId: "combat_music_arvanian_1", requirements: { minInfluence: 0, maxInfluence: 999, canRepeat: false }, influenceReward: 5, respawnDays: 0, background: "Bin/Contents/Locations/Player_Settlement/Combat/Intro_Combat.png", enemies: [ { personaId: "hrothvar_grimward", x: 7, y: 2 }, { personaId: "valdric_grimward", x: 6, y: 2 } ], lootTable: { gold: { min: 15, max: 30 }, resources: { food: { min: 5, max: 12 }, population: { min: 3, max: 8 } }, capturedSlaves: { male: { min: 0, max: 1, chance: 0.3 }, female: { min: 0, max: 0, chance: 0 } }, guaranteed: [ { itemId: "grimward_insignia", quantity: 1 } ], pools: [ { poolId: "grimward_common", rolls: 1 } ] }, timeSkipMinutes: 120, title: "Castle Gate Defense", description: "Grimward soldiers have breached the outer gate. Cut them down before more pour through!", postBattleNarrative: { victory: { overwhelming: "The gate is secured in moments; the Grimward line snaps and scatters.", standard: "You hold the gate after a grinding exchange. The breach is sealed for now.", barely: "You win, but only just. The gate stands, while your defenders struggle to stay upright." }, defeat: "The gate is lost and you are forced back under a hail of steel." } }, "courtyard_raiders": { id: "courtyard_raiders", companionOnly: false, requirements: { minInfluence: 5, maxInfluence: 999, canRepeat: true }, influenceReward: 8, respawnDays: 2, background: "Bin/Contents/Locations/Player_Settlement/Combat/Intro_Combat.png", enemies: [ { personaId: "grimvald_grimward", x: 7, y: 2 }, { personaId: "hrothvar_grimward", x: 6, y: 1 }, { personaId: "valdric_grimward", x: 8, y: 3 } ], lootTable: { gold: { min: 35, max: 60 }, resources: { food: { min: 12, max: 22 }, population: { min: 6, max: 14 } }, capturedSlaves: { male: { min: 0, max: 2, chance: 0.5 }, female: { min: 0, max: 1, chance: 0.2 } }, guaranteed: [ { itemId: "grimward_insignia", quantity: 1 } ], pools: [ { poolId: "grimward_common", rolls: 2 }, { poolId: "grimward_rare", rolls: 1 } ] }, timeSkipMinutes: 180, title: "Courtyard Raiders", description: "A veteran leads two soldiers through the courtyard, torching everything in sight. Stop them before they reach the keep.", postBattleNarrative: { victory: { overwhelming: "The raiders fall before they can torch the yard. The keep still breathes.", standard: "You drive the raiders off after a punishing fight. The courtyard smolders but stands.", barely: "You barely stop the raiders. The courtyard burns and your soldiers limp away." }, defeat: "The raiders overrun the yard, and you are forced to retreat toward the keep." } }, "rescue_family_ambush": { id: "rescue_family_ambush", companionOnly: false, requirements: { minInfluence: 10, maxInfluence: 999, canRepeat: false }, influenceReward: 12, respawnDays: 0, background: "Bin/Contents/Locations/Player_Settlement/Combat/Intro_Combat.png", enemies: [ { personaId: "bjalric_grimward", x: 7, y: 2 }, { personaId: "thyra_grimward", x: 5, y: 1 }, { personaId: "valdric_grimward", x: 8, y: 3 } ], lootTable: { gold: { min: 50, max: 85 }, resources: { food: { min: 15, max: 28 }, population: { min: 8, max: 18 } }, capturedSlaves: { male: { min: 1, max: 2, chance: 0.6 }, female: { min: 0, max: 1, chance: 0.3 } }, guaranteed: [ { itemId: "grimward_insignia", quantity: 2 }, { itemId: "family_wing_key", quantity: 1 } ], pools: [ { poolId: "grimward_common", rolls: 2 }, { poolId: "grimward_rare", rolls: 1 } ] }, timeSkipMinutes: 200, title: "Family Wing Ambush", description: "Grimward soldiers block the path to your mother and sister. A sergeant barks orders while an archer covers the hall.", postBattleNarrative: { victory: { overwhelming: "You carve a clean path to the family wing. The guards break and flee.", standard: "You cut through the ambush and clear the hall. The way to your family opens.", barely: "You stagger through the ambush, bloodied but victorious. The hall is yours, at a steep price." }, defeat: "The ambush holds. You are driven back before you can reach the family wing." } }, "aid_father_battle": { id: "aid_father_battle", companionOnly: false, requirements: { minInfluence: 15, maxInfluence: 999, canRepeat: false }, musicId: "combat_music_arvanian_1", influenceReward: 18, respawnDays: 0, background: "Bin/Contents/Locations/Player_Settlement/Combat/Intro_Combat.png", enemies: [ { personaId: "fenvar_grimward", x: 7, y: 2 }, { personaId: "grimvald_grimward", x: 6, y: 1 }, { personaId: "torvald_grimward", x: 8, y: 3 } ], lootTable: { gold: { min: 70, max: 120 }, resources: { food: { min: 20, max: 38 }, population: { min: 12, max: 25 } }, capturedSlaves: { male: { min: 1, max: 3, chance: 0.7 }, female: { min: 0, max: 1, chance: 0.25 } }, guaranteed: [ { itemId: "grimward_captain_seal", quantity: 1 }, { itemId: "chain_links", quantity: 2 } ], pools: [ { poolId: "grimward_common", rolls: 3 }, { poolId: "grimward_rare", rolls: 2 } ] }, timeSkipMinutes: 240, title: "Father's Last Stand", description: "Your father is surrounded by Grimward elites. Captain Fenvar leads the assault—break through and turn the tide!", postBattleNarrative: { victory: { overwhelming: "Fenvar's line collapses under your charge and your father's flank is saved.", standard: "You break the siege around your father, though the cost is heavy.", barely: "You reach your father just in time, winning by sheer will. The battle leaves deep scars." }, defeat: "You are pushed back and your father's line buckles under the pressure." } }, "grimward_warlord_showdown": { id: "grimward_warlord_showdown", companionOnly: false, requirements: { minInfluence: 25, maxInfluence: 999, canRepeat: false }, influenceReward: 30, respawnDays: 0, background: "Bin/Contents/Locations/Greenwood_Forest/Combat/Southwood_Forest_Hideout_Day.png", enemies: [ { personaId: "draegor_grimward", x: 7, y: 2 }, { personaId: "ragnarr_grimward", x: 5, y: 1 }, { personaId: "kolvar_grimward", x: 9, y: 3 } ], lootTable: { gold: { min: 150, max: 250 }, resources: { food: { min: 40, max: 70 }, population: { min: 25, max: 50 } }, capturedSlaves: { male: { min: 2, max: 4, chance: 0.8 }, female: { min: 1, max: 2, chance: 0.5 } }, guaranteed: [ { itemId: "grimward_warlord_crown", quantity: 1 }, { itemId: "draegor_blade", quantity: 1 }, { itemId: "chain_links", quantity: 5 } ], pools: [ { poolId: "grimward_rare", rolls: 3 }, { poolId: "grimward_epic", rolls: 1 } ] }, timeSkipMinutes: 300, title: "The Usurper's Reckoning", description: "Draegor Grimward himself stands before you, flanked by his maddened berserkers. End this war—end him.", postBattleNarrative: { victory: { overwhelming: "Draegor falls and his war band shatters. The war turns in your favor.", standard: "You defeat the warlord after a brutal clash. The berserkers finally yield.", barely: "You win at the edge of ruin, standing over Draegor with trembling hands." }, defeat: "Draegor's onslaught breaks your line and you are forced to withdraw." } }, "grimward_patrol": { id: "grimward_patrol", companionOnly: true, requirements: { minInfluence: 10, maxInfluence: 999, canRepeat: true }, influenceReward: 6, respawnDays: 2, background: "Bin/Contents/Locations/Greenwood_Forest/Combat/Southwood_Forest_Hideout_Day.png", enemies: [ { personaId: "eskil_grimward", x: 7, y: 2 }, { personaId: "hrothvar_grimward", x: 6, y: 1 }, { personaId: "valdric_grimward", x: 8, y: 3 } ], lootTable: { gold: { min: 25, max: 45 }, resources: { food: { min: 10, max: 18 }, population: { min: 5, max: 12 } }, capturedSlaves: { male: { min: 0, max: 2, chance: 0.45 }, female: { min: 0, max: 1, chance: 0.2 } }, guaranteed: [ { itemId: "grimward_insignia", quantity: 1 } ], pools: [ { poolId: "grimward_common", rolls: 2 } ] }, timeSkipMinutes: 180, title: "Grimward Patrol", description: "A Grimward scout leads a small patrol through the region, searching for survivors and stragglers.", postBattleNarrative: { victory: { overwhelming: "The patrol crumbles in moments and the road is yours.", standard: "You disperse the patrol and take what they carried.", barely: "You scrape out the win and watch for reinforcements in the trees." }, defeat: "The patrol drives you off, leaving the region unsafe." } }, "grimward_war_party": { id: "grimward_war_party", companionOnly: true, requirements: { minInfluence: 20, maxInfluence: 999, canRepeat: true }, influenceReward: 12, respawnDays: 3, background: "Bin/Contents/Locations/Greenwood_Forest/Combat/Southwood_Forest_Hideout_Day.png", enemies: [ { personaId: "skarric_grimward", x: 7, y: 2 }, { personaId: "torvald_grimward", x: 6, y: 1 }, { personaId: "thyra_grimward", x: 8, y: 3 } ], lootTable: { gold: { min: 55, max: 95 }, resources: { food: { min: 18, max: 32 }, population: { min: 10, max: 22 } }, capturedSlaves: { male: { min: 1, max: 3, chance: 0.65 }, female: { min: 0, max: 2, chance: 0.35 } }, guaranteed: [ { itemId: "grimward_insignia", quantity: 2 }, { itemId: "chain_links", quantity: 1 } ], pools: [ { poolId: "grimward_common", rolls: 2 }, { poolId: "grimward_rare", rolls: 2 } ] }, timeSkipMinutes: 240, title: "Grimward War Party", description: "An elite raiding party roams the countryside. A scarred sergeant leads a veteran and an archer on the hunt.", postBattleNarrative: { victory: { overwhelming: "The war party is crushed and their banners fall into the mud.", standard: "You defeat the war party after a fierce melee. The hunt ends.", barely: "You survive the war party's charge, barely. The field is yours, but quiet." }, defeat: "The war party overwhelms you and disappears back into the wild." } } }>>
<<run setup.enemyPersonas = { "garron_pitchfire": { id: "garron_pitchfire", name: "Garron Pitchfire", portrait: "portraits/slaves/bandit_male.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP1/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Bandits/NPC_1/", weaponType: "Hammer", race: "human", gender: "male", title: "Torchbear Tyrant", origin: "Bandit", traits: ["Aggressive", "Unruly"], biography: "Garron brands caravans with a burning iron to mark them as tithe paid to his gang.", value: 118, slaveTemplateId: "slave_template_bandit_male", baseStats: { hp: 6, maxHp: 6, attack: 2, defense: 1, movement: 1, range: 1, diplomacy: 0, intimidation: 2, deception: 0 }, aiType: "bruiser", behavior: { neverRetreats: true, tunnelVision: true, intimidatingPresence: true }, skills: [], stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 0, Happiness: 1, Beauty: 1, Charisma: 1, Health: 6, Stamina: 7, Affection: 0 }, skills: { Melee: 1, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 1, Sneak: 1 }, states: { Fear: 5, Defiance: 13, Stress: 7, Respect: 3, Corruption: 6, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 1, Arousal: 0 } } }, "brunn_ironskull": { id: "brunn_ironskull", name: "Brunn Ironskull", portrait: "portraits/slaves/bandit_male.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP2/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Bandits/NPC_2/", weaponType: "Mace", race: "human", gender: "male", title: "Chain-Break Raider", origin: "Bandit", traits: ["Aggressive", "Unruly"], biography: "Once a caravan guard who turned on his convoy after a brutal winter, Brunn now runs tolls on the King's Road.", value: 120, slaveTemplateId: "slave_template_bandit_male", baseStats: { hp: 6, maxHp: 6, attack: 2, defense: 1, movement: 1, range: 1, diplomacy: 0, intimidation: 2, deception: 0 }, aiType: "bruiser", behavior: { neverRetreats: true, tunnelVision: true, intimidatingPresence: true }, skills: [], stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 0, Happiness: 1, Beauty: 1, Charisma: 1, Health: 6, Stamina: 7, Affection: 0 }, skills: { Melee: 1, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 1, Sneak: 1 }, states: { Fear: 5, Defiance: 13, Stress: 7, Respect: 3, Corruption: 6, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 1, Arousal: 0 } } }, "joric_steelgut": { id: "joric_steelgut", name: "Joric Steelgut", portrait: "portraits/slaves/bandit_male.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP3/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Bandits/NPC_3/", weaponType: "Hammer", race: "human", gender: "male", title: "Alehouse Crusher", origin: "Bandit", traits: ["Aggressive", "Unruly"], biography: "Joric was a brewer's enforcer before he started smashing wagons for their barrels.", value: 110, slaveTemplateId: "slave_template_bandit_male", baseStats: { hp: 6, maxHp: 6, attack: 2, defense: 1, movement: 1, range: 1, diplomacy: 0, intimidation: 2, deception: 0 }, aiType: "bruiser", behavior: { neverRetreats: true, tunnelVision: true, intimidatingPresence: true }, skills: [], stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 0, Happiness: 1, Beauty: 1, Charisma: 1, Health: 6, Stamina: 7, Affection: 0 }, skills: { Melee: 1, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 1, Sneak: 1 }, states: { Fear: 5, Defiance: 13, Stress: 7, Respect: 3, Corruption: 6, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 1, Arousal: 0 } } }, "anise_quickblade": { id: "anise_quickblade", name: "Anise Quickblade", portrait: "portraits/slaves/bandit_female.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP4/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Bandits/NPC_1/", weaponType: "Unarmed", race: "human", gender: "female", title: "Knife Dancer", origin: "Bandit", traits: ["Cunning", "Observant"], biography: "Anise prides herself on carving her name into wanted posters before the ink dries.", value: 95, slaveTemplateId: "slave_template_bandit_female", baseStats: { hp: 5, maxHp: 5, attack: 1, defense: 1, movement: 3, range: 1, diplomacy: 0, intimidation: 1, deception: 1 }, aiType: "opportunist", behavior: { cowardThreshold: 0.5, packHunter: true, targetsPriority: "lowest_hp", preferredRange: 3 }, skills: ["Throwing Knife"], stats: { attributes: { Strength: 1, Dexterity: 2, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 1, Happiness: 1, Beauty: 1, Charisma: 1, Health: 5, Stamina: 6, Affection: 0 }, skills: { Melee: 1, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 1, Sneak: 1 }, states: { Fear: 6, Defiance: 11, Stress: 8, Respect: 4, Corruption: 5, Devotion: 0 }, sexual: { Vaginal: 2, Anal: 1, Oral: 2, Kissing: 3, Arousal: 2 } } }, "lysa_daggersong": { id: "lysa_daggersong", name: "Lysa Daggersong", portrait: "portraits/slaves/bandit_female.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP5/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Bandits/NPC_2/", weaponType: "Unarmed", race: "human", gender: "female", title: "Shadow Chorister", origin: "Bandit", traits: ["Cunning", "Observant"], biography: "Lysa hums gallows lullabies while stalking the verge for travelers.", value: 100, slaveTemplateId: "slave_template_bandit_female", baseStats: { hp: 5, maxHp: 5, attack: 1, defense: 1, movement: 3, range: 1, diplomacy: 0, intimidation: 1, deception: 1 }, aiType: "opportunist", behavior: { cowardThreshold: 0.5, packHunter: true, targetsPriority: "lowest_hp", preferredRange: 3 }, skills: ["Throwing Knife"], stats: { attributes: { Strength: 1, Dexterity: 2, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 1, Happiness: 1, Beauty: 1, Charisma: 1, Health: 5, Stamina: 6, Affection: 0 }, skills: { Melee: 1, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 1, Sneak: 1 }, states: { Fear: 6, Defiance: 11, Stress: 8, Respect: 4, Corruption: 5, Devotion: 0 }, sexual: { Vaginal: 2, Anal: 1, Oral: 2, Kissing: 3, Arousal: 2 } } }, "mira_cutlass": { id: "mira_cutlass", name: "Mira Cutlass", portrait: "portraits/slaves/bandit_female.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP6/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Bandits/NPC_3/", weaponType: "Mace", race: "human", gender: "female", title: "River Corsair", origin: "Bandit", traits: ["Cunning", "Observant"], biography: "Mira sailed river barges until she hijacked her own and fled inland with the spoils.", value: 102, slaveTemplateId: "slave_template_bandit_female", baseStats: { hp: 5, maxHp: 5, attack: 1, defense: 1, movement: 3, range: 1, diplomacy: 0, intimidation: 1, deception: 1 }, aiType: "opportunist", behavior: { cowardThreshold: 0.5, packHunter: true, targetsPriority: "lowest_hp", preferredRange: 3 }, skills: ["Throwing Knife"], stats: { attributes: { Strength: 1, Dexterity: 2, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 1, Happiness: 1, Beauty: 1, Charisma: 1, Health: 5, Stamina: 6, Affection: 0 }, skills: { Melee: 1, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 1, Sneak: 1 }, states: { Fear: 6, Defiance: 11, Stress: 8, Respect: 4, Corruption: 5, Devotion: 0 }, sexual: { Vaginal: 2, Anal: 1, Oral: 2, Kissing: 3, Arousal: 2 } } }, "hrothvar_grimward": { id: "hrothvar_grimward", name: "Hrothvar Grimward", portrait: "portraits/slaves/grimward_male.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP7/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Arvanian/NPC_1/", weaponType: "Axe", race: "human", gender: "male", title: "Gate Breaker", origin: "Clan Grimward", traits: ["Aggressive", "Reckless"], biography: "Young blood eager to earn his place. Volunteered to lead the breach at your castle gates.", value: 90, slaveTemplateId: "slave_template_grimward_male", baseStats: { hp: 5, maxHp: 5, attack: 2, defense: 1, movement: 1, range: 1, diplomacy: 0, intimidation: 1, deception: 0 }, aiType: "bruiser", behavior: { neverRetreats: false, tunnelVision: true, intimidatingPresence: false }, skills: [], stats: { attributes: { Strength: 1, Dexterity: 1, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 0, Happiness: 1, Beauty: 1, Charisma: 0, Health: 5, Stamina: 7, Affection: 0 }, skills: { Melee: 1, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 1, Sneak: 1 }, states: { Fear: 4, Defiance: 10, Stress: 6, Respect: 2, Corruption: 4, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 1, Arousal: 0 } } }, "valdric_grimward": { id: "valdric_grimward", name: "Valdric Grimward", portrait: "portraits/slaves/grimward_male.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP1/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Arvanian/NPC_2/", weaponType: "Axe_Shield", race: "human", gender: "male", title: "Shield Bearer", origin: "Clan Grimward", traits: ["Disciplined", "Stoic"], biography: "Veteran of a dozen raids. Learned that survival means staying behind his shield.", value: 95, slaveTemplateId: "slave_template_grimward_male", baseStats: { hp: 6, maxHp: 6, attack: 2, defense: 1, movement: 1, range: 1, diplomacy: 0, intimidation: 1, deception: 0 }, aiType: "bruiser", behavior: { neverRetreats: false, tunnelVision: false, intimidatingPresence: false }, skills: ["Shield Block"], stats: { attributes: { Strength: 1, Dexterity: 1, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 0, Happiness: 1, Beauty: 1, Charisma: 1, Health: 6, Stamina: 7, Affection: 0 }, skills: { Melee: 1, Ranged: 0, Athletics: 1, Survival: 1, Intimidation: 1, Sneak: 0 }, states: { Fear: 3, Defiance: 8, Stress: 5, Respect: 4, Corruption: 3, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 1, Arousal: 0 } } }, "thyra_grimward": { id: "thyra_grimward", name: "Thyra Grimward", portrait: "portraits/slaves/grimward_female.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP2/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Arvanian/NPC_3/", weaponType: "Sword", race: "human", gender: "female", title: "Silent Shot", origin: "Clan Grimward", traits: ["Cunning", "Patient"], biography: "She picks off sentries before the main force arrives. Your guards never heard her arrows.", value: 100, slaveTemplateId: "slave_template_grimward_female", baseStats: { hp: 5, maxHp: 5, attack: 1, defense: 1, movement: 3, range: 4, diplomacy: 0, intimidation: 0, deception: 1 }, aiType: "opportunist", behavior: { cowardThreshold: 0.4, packHunter: true, targetsPriority: "lowest_hp", preferredRange: 4 }, skills: ["Longbow"], stats: { attributes: { Strength: 1, Dexterity: 2, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 0, Happiness: 1, Beauty: 1, Charisma: 1, Health: 5, Stamina: 6, Affection: 0 }, skills: { Melee: 1, Ranged: 2, Athletics: 1, Survival: 1, Intimidation: 1, Sneak: 1 }, states: { Fear: 5, Defiance: 9, Stress: 6, Respect: 4, Corruption: 4, Devotion: 0 }, sexual: { Vaginal: 1, Anal: 0, Oral: 1, Kissing: 2, Arousal: 1 } } }, "eskil_grimward": { id: "eskil_grimward", name: "Eskil Grimward", portrait: "portraits/slaves/grimward_male.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP3/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Arvanian/NPC_4/", weaponType: "Sword", race: "human", gender: "male", title: "Deadeye Scout", origin: "Clan Grimward", traits: ["Observant", "Cautious"], biography: "Scouts ahead for the war party, marking targets for the berserkers.", value: 95, slaveTemplateId: "slave_template_grimward_male", baseStats: { hp: 5, maxHp: 5, attack: 1, defense: 1, movement: 3, range: 4, diplomacy: 0, intimidation: 1, deception: 1 }, aiType: "opportunist", behavior: { cowardThreshold: 0.5, packHunter: true, targetsPriority: "lowest_hp", preferredRange: 4 }, skills: ["Shortbow"], stats: { attributes: { Strength: 1, Dexterity: 2, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 0, Happiness: 1, Beauty: 1, Charisma: 1, Health: 5, Stamina: 6, Affection: 0 }, skills: { Melee: 1, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 1, Sneak: 1 }, states: { Fear: 5, Defiance: 8, Stress: 6, Respect: 4, Corruption: 4, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 1, Arousal: 0 } } }, "grimvald_grimward": { id: "grimvald_grimward", name: "Grimvald Grimward", portrait: "portraits/slaves/grimward_male.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP4/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Arvanian/NPC_1/", weaponType: "Axe", race: "human", gender: "male", title: "Iron Hound", origin: "Clan Grimward", traits: ["Aggressive", "Unruly"], biography: "Survived a dozen campaigns. Every scar is a lesson he's eager to teach.", value: 130, slaveTemplateId: "slave_template_grimward_male", baseStats: { hp: 6, maxHp: 6, attack: 2, defense: 1, movement: 1, range: 1, diplomacy: 0, intimidation: 1, deception: 0 }, aiType: "bruiser", behavior: { neverRetreats: true, tunnelVision: true, intimidatingPresence: true }, skills: [], stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 0, Happiness: 1, Beauty: 1, Charisma: 1, Health: 6, Stamina: 8, Affection: 0 }, skills: { Melee: 2, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 1, Sneak: 1 }, states: { Fear: 4, Defiance: 14, Stress: 7, Respect: 3, Corruption: 6, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 1, Arousal: 0 } } }, "torvald_grimward": { id: "torvald_grimward", name: "Torvald Grimward", portrait: "portraits/slaves/grimward_male.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP5/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Arvanian/NPC_2/", weaponType: "Axe", race: "human", gender: "male", title: "The Cleaver", origin: "Clan Grimward", traits: ["Brutal", "Methodical"], biography: "Known for brutal efficiency. He doesn't waste movement—every swing ends a life.", value: 135, slaveTemplateId: "slave_template_grimward_male", baseStats: { hp: 6, maxHp: 6, attack: 3, defense: 1, movement: 1, range: 1, diplomacy: 0, intimidation: 2, deception: 0 }, aiType: "bruiser", behavior: { neverRetreats: true, tunnelVision: true, intimidatingPresence: true }, skills: ["Cleave"], stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 0, Happiness: 0, Beauty: 0, Charisma: 0, Health: 6, Stamina: 8, Affection: 0 }, skills: { Melee: 2, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 2, Sneak: 0 }, states: { Fear: 3, Defiance: 15, Stress: 6, Respect: 3, Corruption: 7, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 0, Arousal: 0 } } }, "ragnarr_grimward": { id: "ragnarr_grimward", capturable: false, name: "Ragnarr Grimward", portrait: "portraits/slaves/grimward_male.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP6/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Arvanian/NPC_3/", weaponType: "Axe", race: "human", gender: "male", title: "The Mad Bull", origin: "Clan Grimward", traits: ["Aggressive", "Reckless"], biography: "Charges into battle with reckless abandon, caring nothing for his own safety.", value: 140, slaveTemplateId: "slave_template_grimward_male", baseStats: { hp: 6, maxHp: 6, attack: 3, defense: 1, movement: 2, range: 1, diplomacy: 0, intimidation: 2, deception: 0 }, aiType: "bruiser", behavior: { neverRetreats: true, tunnelVision: true, intimidatingPresence: true, berserkAtLowHp: true }, skills: ["Reckless Strike"], stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 1, Willpower: 1, Discipline: 0, Kindness: 0, Happiness: 1, Beauty: 0, Charisma: 0, Health: 6, Stamina: 9, Affection: 0 }, skills: { Melee: 2, Ranged: 0, Athletics: 1, Survival: 1, Intimidation: 2, Sneak: 0 }, states: { Fear: 2, Defiance: 18, Stress: 10, Respect: 2, Corruption: 8, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 0, Arousal: 0 } } }, "kolvar_grimward": { id: "kolvar_grimward", capturable: false, name: "Kolvar Grimward", portrait: "portraits/slaves/grimward_male.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP7/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Arvanian/NPC_4/", weaponType: "Axe", race: "human", gender: "male", title: "Fury Unleashed", origin: "Clan Grimward", traits: ["Aggressive", "Unruly"], biography: "Works himself into a frenzy before battle. His war cries echo through the burning villages.", value: 145, slaveTemplateId: "slave_template_grimward_male", baseStats: { hp: 6, maxHp: 6, attack: 3, defense: 1, movement: 2, range: 1, diplomacy: 0, intimidation: 2, deception: 0 }, aiType: "bruiser", behavior: { neverRetreats: true, tunnelVision: true, intimidatingPresence: true, berserkAtLowHp: true }, skills: ["War Cry"], stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 0, Happiness: 1, Beauty: 1, Charisma: 0, Health: 6, Stamina: 9, Affection: 0 }, skills: { Melee: 2, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 2, Sneak: 0 }, states: { Fear: 2, Defiance: 17, Stress: 9, Respect: 2, Corruption: 7, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 0, Arousal: 0 } } }, "bjalric_grimward": { id: "bjalric_grimward", name: "Bjalric Grimward", portrait: "portraits/slaves/grimward_male.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP1/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Arvanian/NPC_1/", weaponType: "Sword", race: "human", gender: "male", title: "Taskmaster", origin: "Clan Grimward", traits: ["Disciplined", "Cruel"], biography: "Commands his squads with iron discipline. Failure is punished with the lash.", value: 150, slaveTemplateId: "slave_template_grimward_male", baseStats: { hp: 6, maxHp: 6, attack: 2, defense: 2, movement: 1, range: 1, diplomacy: 0, intimidation: 2, deception: 0 }, aiType: "bruiser", behavior: { neverRetreats: true, tunnelVision: false, intimidatingPresence: true, commandsAllies: true }, skills: ["Rally"], stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 0, Happiness: 1, Beauty: 1, Charisma: 1, Health: 6, Stamina: 8, Affection: 0 }, skills: { Melee: 1, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 2, Sneak: 1 }, states: { Fear: 3, Defiance: 14, Stress: 6, Respect: 5, Corruption: 6, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 1, Arousal: 0 } } }, "skarric_grimward": { id: "skarric_grimward", name: "Skarric Grimward", portrait: "portraits/slaves/grimward_male.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP2/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Arvanian/NPC_2/", weaponType: "Axe_Shield", race: "human", gender: "male", title: "The Scarred", origin: "Clan Grimward", traits: ["Stoic", "Veteran"], biography: "His face is a map of old wounds. Each scar represents a man who thought he could kill him.", value: 155, slaveTemplateId: "slave_template_grimward_male", baseStats: { hp: 6, maxHp: 6, attack: 2, defense: 2, movement: 1, range: 1, diplomacy: 0, intimidation: 1, deception: 0 }, aiType: "bruiser", behavior: { neverRetreats: true, tunnelVision: false, intimidatingPresence: true }, skills: ["Endure"], stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 0, Happiness: 0, Beauty: 0, Charisma: 1, Health: 6, Stamina: 8, Affection: 0 }, skills: { Melee: 2, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 1, Sneak: 1 }, states: { Fear: 2, Defiance: 13, Stress: 5, Respect: 6, Corruption: 5, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 0, Arousal: 0 } } }, "fenvar_grimward": { id: "fenvar_grimward", name: "Fenvar Grimward", portrait: "portraits/slaves/grimward_male.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP3/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Arvanian/NPC_3/", weaponType: "Sword", race: "human", gender: "male", title: "The Night Wolf", origin: "Clan Grimward", traits: ["Cunning", "Ruthless"], biography: "Led the night attack on the outer villages. Believes terror is the sharpest weapon.", value: 180, slaveTemplateId: "slave_template_grimward_male", baseStats: { hp: 6, maxHp: 6, attack: 3, defense: 2, movement: 1, range: 1, diplomacy: 1, intimidation: 2, deception: 1 }, aiType: "bruiser", behavior: { neverRetreats: true, tunnelVision: false, intimidatingPresence: true, commandsAllies: true }, skills: ["Intimidating Strike"], stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 0, Happiness: 1, Beauty: 1, Charisma: 1, Health: 6, Stamina: 9, Affection: 0 }, skills: { Melee: 2, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 2, Sneak: 1 }, states: { Fear: 2, Defiance: 16, Stress: 5, Respect: 4, Corruption: 8, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 1, Arousal: 0 } } }, "stormald_grimward": { id: "stormald_grimward", name: "Stormald Grimward", portrait: "portraits/slaves/grimward_male.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP4/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Arvanian/NPC_4/", weaponType: "Axe", race: "human", gender: "male", title: "Thunder's Voice", origin: "Clan Grimward", traits: ["Commanding", "Brutal"], biography: "His booming voice rallies troops and breaks morale. Crushed three lords before this siege.", value: 185, slaveTemplateId: "slave_template_grimward_male", baseStats: { hp: 6, maxHp: 6, attack: 3, defense: 2, movement: 1, range: 1, diplomacy: 0, intimidation: 2, deception: 1 }, aiType: "bruiser", behavior: { neverRetreats: true, tunnelVision: false, intimidatingPresence: true, commandsAllies: true }, skills: ["Battle Cry"], stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 1, Willpower: 1, Discipline: 1, Kindness: 0, Happiness: 1, Beauty: 1, Charisma: 1, Health: 6, Stamina: 9, Affection: 0 }, skills: { Melee: 2, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 2, Sneak: 0 }, states: { Fear: 2, Defiance: 17, Stress: 5, Respect: 5, Corruption: 7, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 1, Arousal: 0 } } }, "draegor_grimward": { id: "draegor_grimward", capturable: false, name: "Draegor Grimward", portrait: "portraits/slaves/grimward_warlord.png", characterPath: "Bin/Contents/Characters/Human_Storyline/Others/RP5/", animationPath: "Bin/Contents/Combat/Animation/Enemy/Arvanian/NPC_1/", weaponType: "Sword", race: "human", gender: "male", title: "The Usurper", origin: "Clan Grimward", traits: ["Ambitious", "Ruthless", "Cunning"], biography: "Head of Clan Grimward. Claims your father's lands by right of blood feud—a feud he invented to justify conquest.", value: 300, slaveTemplateId: "slave_template_grimward_warlord", baseStats: { hp: 6, maxHp: 6, attack: 3, defense: 2, movement: 1, range: 1, diplomacy: 1, intimidation: 2, deception: 1 }, aiType: "bruiser", behavior: { neverRetreats: true, tunnelVision: false, intimidatingPresence: true, commandsAllies: true, bossUnit: true }, skills: ["Warlord's Fury", "Rally Troops"], stats: { attributes: { Strength: 2, Dexterity: 1, Intelligence: 1, Willpower: 2, Discipline: 1, Kindness: 0, Happiness: 1, Beauty: 1, Charisma: 1, Health: 6, Stamina: 10, Affection: 0 }, skills: { Melee: 2, Ranged: 1, Athletics: 1, Survival: 1, Intimidation: 2, Sneak: 1 }, states: { Fear: 1, Defiance: 20, Stress: 4, Respect: 6, Corruption: 10, Devotion: 0 }, sexual: { Vaginal: 0, Anal: 0, Oral: 0, Kissing: 1, Arousal: 0 } } } }>>
<<run setup.lootPools = (window.GameItemCatalog && window.GameItemCatalog.lootPools) ? window.GameItemCatalog.lootPools : {}>>
/* city status */ <<set $cityStats to { "happiness": 70, /* Initial Happiness from city builder */ "security": 0, /* No initial security effect */ "population": 0, /* Initial population from city builder */ "maxPopulation": 0, /* Initial max population from city builder */ "loyalty": 50, /* Default Loyalty */ "crime": 0, /* Default Crime */ "slaves": 0 /* Default Slaves */ }>> <<set $buildingDefinitions to { /* Housing */ "house": { id: "house", name: 'House', category: 'housing', icon: '🏠', width: 4, height: 4, description: 'Provides housing for 4 citizens.', baseCost: { gold: 100, wood: 30 }, constructionTime: 2, produces: { gold: 2 }, /* Small tax */ provides: { maxPopulation: 4 }, happiness: 1, /* Each house provides +1 happiness */ effects: {}, maxCount: null, needsManager: false, maxWorkers: 0, maxManagers: 1, baseOutput: 2, // Gold output for management system upgradeCost: level => (100 + 30) * (level * 1.4), statProfile: { training: { residents: { attributes: { Happiness: 0.2, Kindness: 0.1 } } } }, image: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Houses/HousesT1.png", listImage: "Bin/Contents/Locations/Player_Settlement/Buildings_UI/Houses.jpg", topDownImage: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Houses/HousesT1.png", topDownImageTier2: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Houses/HousesT2.png", topDownImageTier3: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Houses/HousesT3.png", // Display configuration for building info panel displayInfo: { sections: [ { type: "overview", stats: [ { label: "Occupancy", value: "occupancy" }, { label: "Comfort Level", value: "comfort", suffix: "/100" } ] }, { title: "Residents", type: "residents", condition: "hasResidents", stats: [ { label: "Living Here", value: "residentNames" } ] } ] } }, /* Production */ "woodcutter": { id: "woodcutter", name: 'Woodcutter', category: 'production', icon: '🪓', width: 8, height: 8, description: 'Harvests wood resources.', baseCost: { gold: 100 }, constructionTime: 2, produces: { wood: 5 }, provides: {}, effects: {}, maxCount: null, needsManager: true, maxWorkers: 4, maxManagers: 1, baseOutput: 5, // Wood output for management system upgradeCost: level => 100 * (level * 1.5), statProfile: { workers: { attributes: { Strength: 1, Stamina: 0.6 }, skills: { Survival: 0.5 } }, managers: { attributes: { Discipline: 1 }, skills: { Administration: 0.8 } }, training: { workers: { attributes: { Strength: 0.4 }, skills: { Survival: 0.2 } } } }, image: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Woodcutter/WoodcutterT1.png", listImage: "Bin/Contents/Locations/Player_Settlement/Buildings_UI/Woodcutter.jpg", topDownImage: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Woodcutter/WoodcutterT1.png", topDownImageTier2: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Woodcutter/WoodcutterT2.png", topDownImageTier3: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Woodcutter/WoodcutterT3.png", // Display configuration for building info panel displayInfo: { sections: [ { type: "current_building", condition: "hasSelectedBuilding", stats: [ { label: "Wood Output", value: "currentWoodOutput", suffix: " / day", calculation: "efficiency.totalOutput.wood" }, { label: "Efficiency", value: "currentEfficiencyBonus", prefix: "+", suffix: "%", calculation: "(efficiency.productionMultiplier - 1) * 100" } ] } ] } }, "stonemine": { id: "stonemine", name: 'Stone Mine', category: 'production', icon: '⛏️', width: 8, height: 8, description: 'Quarries stone resources.', baseCost: { gold: 150, wood: 20 }, constructionTime: 3, produces: { stone: 4 }, provides: {}, effects: {}, maxCount: null, needsManager: true, maxWorkers: 4, maxManagers: 1, baseOutput: 4, // Stone output for management system upgradeCost: level => (150 + 20) * (level * 1.5), statProfile: { workers: { attributes: { Strength: 1, Discipline: 0.8 }, skills: { "Melee Combat": 0.5 } }, managers: { attributes: { Willpower: 1 }, skills: { Administration: 0.8 } }, training: { workers: { attributes: { Strength: 0.3, Discipline: 0.2 } } } }, image: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Stonemine/StonemineT1.png", listImage: "Bin/Contents/Locations/Player_Settlement/Buildings_UI/Stonemine.jpg", topDownImage: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Stonemine/StonemineT1.png", topDownImageTier2: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Stonemine/StonemineT2.png", topDownImageTier3: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Stonemine/StonemineT3.png", // Display configuration for building info panel displayInfo: { sections: [ { type: "current_building", condition: "hasSelectedBuilding", stats: [ { label: "Stone Output", value: "currentStoneOutput", suffix: " / day", calculation: "efficiency.totalOutput.stone" }, { label: "Efficiency", value: "currentEfficiencyBonus", prefix: "+", suffix: "%", calculation: "(efficiency.productionMultiplier - 1) * 100" } ] } ] } }, "farm": { id: "farm", name: 'Farm', category: 'production', icon: '🌾', width: 8, height: 8, description: 'Grows food. Production varies by season.', baseCost: { gold: 200, wood: 40 }, constructionTime: 4, produces: { food: 10 }, provides: {}, effects: { happiness: 1 }, maxCount: null, seasonal: true, needsManager: true, maxWorkers: 4, maxManagers: 1, baseOutput: 10, // Food output for management system foodProduction: 10, // For food balance calculation upgradeCost: level => (200 + 40) * (level * 1.5), statProfile: { workers: { attributes: { Intelligence: 0.6, Stamina: 0.5 }, skills: { Gardening: 1 } }, managers: { attributes: { Administration: 0.8 }, skills: { Science: 0.7 } }, training: { workers: { attributes: { Stamina: 0.2 }, skills: { Gardening: 0.4 } } } }, image: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Farm/FarmT1.png", listImage: "Bin/Contents/Locations/Player_Settlement/Buildings_UI/Farm.jpg", topDownImage: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Farm/FarmT1.png", topDownImageTier2: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Farm/FarmT2.png", topDownImageTier3: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Farm/FarmT3.png", // Display configuration for building info panel displayInfo: { sections: [ { type: "current_building", condition: "hasSelectedBuilding", stats: [ { label: "Food Output", value: "currentFoodOutput", suffix: " / day", calculation: "efficiency.totalOutput.food" }, { label: "Efficiency", value: "currentEfficiencyBonus", prefix: "+", suffix: "%", calculation: "(efficiency.productionMultiplier - 1) * 100" } ] } ] } }, "stonemine": { id: "stonemine", name: "Stone Mine", category: 'production', icon: '🪨', width: 8, height: 8, description: "A specialized mine focused on extracting stones.", baseCost: { gold: 220, wood: 40 }, constructionTime: 6, produces: { stone: 15 }, provides: {}, effects: { happiness: -1 }, maxCount: null, needsManager: true, maxWorkers: 4, maxManagers: 1, baseOutput: 15, // Stone output for management system upgradeCost: level => (220 + 40) * (level * 1.5), statProfile: { workers: { attributes: { Strength: 1, Discipline: 0.7 }, skills: { Survival: 0.6 } }, managers: { attributes: { Willpower: 0.9 }, skills: { Administration: 0.7 } }, training: { workers: { attributes: { Strength: 0.4 }, skills: { Survival: 0.2 } } } }, image: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Stonemine/StonemineT1.png", listImage: "Bin/Contents/Locations/Player_Settlement/Buildings_UI/Stonemine.jpg", topDownImage: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Stonemine/StonemineT1.png", topDownImageTier2: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Stonemine/StonemineT2.png", topDownImageTier3: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Stonemine/StonemineT3.png" } }>>
<div class="city-building-bottom-panel" id="bottom-panel"> <div class="city-building-main-toolbar"> <!-- Building tools on the left --> <div class="city-building-toolbar-section city-building-build-tools"> <!-- All Buildings Category --> <div class="city-building-category-wrapper"> <button class="city-building-toolbar-btn" data-category="all" title="All Buildings" data-tooltip-title="Buildings" data-tooltip="Open the building list" data-tooltip-placement="top"></button> <div class="city-building-category-popup" id="popup-all" style="display: none;"></div> </div> </div> <!-- Resources on the right --> <div class="city-building-toolbar-section city-building-resources"> <div class="city-building-resource"> <img src="Bin/Contents/UI/Icons/Icon_ResourceGold.png" alt="Gold"> <span id="gold-amount">0</span> </div> <div class="city-building-resource"> <img src="Bin/Contents/UI/Icons/Icon_ResourceWood.png" alt="Wood"> <span id="wood-amount">0</span> </div> <div class="city-building-resource"> <img src="Bin/Contents/UI/Icons/Icon_ResourceStone.png" alt="Stone"> <span id="stone-amount">0</span> </div> <div class="city-building-resource"> <img src="Bin/Contents/UI/Icons/Icon_ResourceFood.png" alt="Food"> <span id="food-amount">0</span> </div> <div class="city-building-resource"> <img src="Bin/Contents/UI/Icons/Icon_ResourcePopulation.png" alt="Food"> <span id="population-amount">0</span> </div> </div> </div> </div>
<!-- Top row --> <div class="top_row"> <button class="icon_button" data-gui-button="castle-management" data-tooltip="Castle Management" data-tooltip-placement="top" onclick="openUIScreen('settlement_overview')"> <img src="Bin/Contents/UI/Buttons/btn_city.png" alt="Castle"> </button> <button class="icon_button" data-gui-button="companions" data-tooltip="Companions" data-tooltip-placement="top" onclick="openUIScreen('companion')"> <img src="Bin/Contents/UI/Buttons/btn_companion.png" alt="Companion"> </button> <button class="icon_button" data-gui-button="followers" data-tooltip="Followers" data-tooltip-placement="top" onclick="openUIScreen('follower')" aria-label="Followers"> <img src="Bin/Contents/UI/Buttons/btn_party.png" alt="Followers"> </button> <button class="icon_button" id="quest-ui-button" data-gui-button="quest-ui" data-tooltip="Quest Board" data-tooltip-placement="top" onclick="openQuestUIPopup()"> <img src="Bin/Contents/UI/Buttons/btn_quest.png" alt="Questboard"> </button> </div> <!-- Bottom row --> <div class="bottom_row"> <button class="icon_button" data-gui-button="world-map" data-tooltip="World Map" data-tooltip-placement="top" onclick="openWorldMap()"> <img src="Bin/Contents/UI/Buttons/btn_map.png" alt="Map"> </button> <button class="icon_button" data-gui-button="prison" data-tooltip="Prison" data-tooltip-placement="top" onclick="openUIScreen('game_prison')"> <img src="Bin/Contents/UI/Buttons/btn_dungeon.png" alt="Prisoner"> </button> <button class="icon_button" data-gui-button="inventory" data-tooltip="Inventory" data-tooltip-placement="top" onclick="openUIScreen('player_inventory')"> <img src="Bin/Contents/UI/Buttons/btn_inventory.png" alt="Inventory"> </button> <button class="icon_button" data-gui-button="messages" data-tooltip="Messages" data-tooltip-placement="top"> <img src="Bin/Contents/UI/Buttons/btn_message.png" alt="Randomrequest"> </button> </div>
window.MarketSystem = (function() { const runtime = { initialized: false, passageHookBound: false, activeHubId: null, renderRetry: { timer: null, attempts: 0 }, domObserver: null, listingTemplateSource: null, uiBound: false, activeFilter: 'all', activeSort: 'demand', summary: null, demandFeed: [], inventorySellCache: {} }; const RENDER_RETRY_LIMIT = 600; const RENDER_RETRY_DELAY = 100; const DEMAND_WINDOW_MS = 7 * 24 * 60 * 60 * 1000; const DEMAND_SIGNAL_LIMIT = 500; const SUPPLY_RECORD_LIMIT = 500; const PRICE_MAX_MODIFIER = 0.5; const PRICE_MIN_MODIFIER = -0.35; const PRICE_DECAY_PER_HOUR = 0.02; const DEFAULT_HUB = { name: 'Town Square Market', areaId: null, buildingId: null, commissionRate: 0.05, buyerTaxRate: 0.02, upkeepPerDay: 15, capacity: 24, specialization: 'general', listings: [], traders: [], demandModifiers: {}, lastRefresh: 0, reports: [], demandMetrics: {}, priceHistory: [], turnoverDaily: 0, lastTrendUpdate: 0 }; const LEGACY_RESOURCE_CATALOG = Object.freeze({ gold: { itemId: 'city_gold_purse', label: 'Coin Purse', stackSize: 25, basePrice: 1, ownerName: 'City Mint', priority: 1, tags: ['currency'] }, food: { itemId: 'grain_sacks', label: 'Sacks of Grain', stackSize: 20, basePrice: 8, ownerName: 'Granary Collective', priority: 0, tags: ['resource', 'food'] }, wood: { itemId: 'timber_bundle', label: 'Timber Bundle', stackSize: 15, basePrice: 6, ownerName: 'Lumber Guild', priority: 2, tags: ['resource', 'wood'] }, stone: { itemId: 'stone_block', label: 'Stone Block', stackSize: 12, basePrice: 9, ownerName: 'Quarry Workers', priority: 3, tags: ['resource', 'stone'] } }); const catalogRuntime = { fallbackByItemId: null }; function getCatalogResources() { if (window.GameItemCatalog && window.GameItemCatalog.resources && Object.keys(window.GameItemCatalog.resources).length) { return window.GameItemCatalog.resources; } return LEGACY_RESOURCE_CATALOG; } function getCatalogByItemId() { if (window.GameItemCatalog && window.GameItemCatalog.byItemId && Object.keys(window.GameItemCatalog.byItemId).length) { return window.GameItemCatalog.byItemId; } if (!catalogRuntime.fallbackByItemId) { catalogRuntime.fallbackByItemId = {}; Object.entries(LEGACY_RESOURCE_CATALOG).forEach(([resourceKey, entry]) => { if (entry && entry.itemId) { catalogRuntime.fallbackByItemId[entry.itemId] = resourceKey; } }); } return catalogRuntime.fallbackByItemId; } function getResourceCatalogEntries() { return Object.entries(getCatalogResources()); } function getItemConfig(resourceKey) { if (!resourceKey) return null; if (window.GameItems && typeof window.GameItems.getResource === 'function') { const resolved = window.GameItems.getResource(resourceKey); if (resolved) { return resolved; } } const resources = getCatalogResources(); if (resources && resources[resourceKey]) { return resources[resourceKey]; } function runMarketCycleAfterCombat(options = {}) { const hubId = resolveHubId(options.hubId); let dailyProcessed = false; if (options.triggerDailyProduction !== false && typeof window.updateSettlementDaily === 'function') { try { window.updateSettlementDaily(); dailyProcessed = true; } catch (error) { console.error('[MarketSystem] Failed to process daily settlement update during combat trigger:', error); } } if (!dailyProcessed) { if (hubId) { try { simulateDailySales(hubId); } catch (error) { console.error('[MarketSystem] Failed to simulate market sales for hub during combat trigger:', hubId, error); } } else { const state = getState(); if (state && state.hubs) { Object.keys(state.hubs).forEach(id => { try { simulateDailySales(id); } catch (error) { console.error('[MarketSystem] Failed to simulate market sales for hub during combat trigger:', id, error); } }); } } } if (hubId) { refreshHub(hubId); } else { const state = getState(); if (state && state.hubs) { Object.keys(state.hubs).forEach(id => refreshHub(id)); } } renderActiveHub(); return { hubId, dailyProcessed }; } return { itemId: resourceKey, label: resourceKey, stackSize: 1, basePrice: 1, ownerName: 'Unknown Seller', priority: 0, tags: [] }; } function resolveResourceKeyFromItemId(itemId) { if (!itemId) return null; const byItemId = getCatalogByItemId(); return byItemId[itemId] || null; } function getPlayerState() { if (typeof window === 'undefined' || !window.setup || typeof window.setup.ensurePlayerState !== 'function') { return null; } try { return window.setup.ensurePlayerState(false); } catch (error) { return null; } } function getPlayerResourceContainer() { const playerState = getPlayerState(); if (playerState && playerState.resources && typeof playerState.resources === 'object') { return { playerState, container: playerState.resources }; } if (typeof State !== 'undefined' && State.variables) { if (!State.variables.playerResources || typeof State.variables.playerResources !== 'object') { State.variables.playerResources = {}; } return { playerState: null, container: State.variables.playerResources }; } return { playerState: playerState || null, container: null }; } function adjustPlayerGold(delta) { if (!delta) return; const { playerState, container } = getPlayerResourceContainer(); if (!container) return; const current = typeof container.gold === 'number' ? container.gold : 0; container.gold = Math.max(0, current + delta); if (playerState && typeof window.setup === 'object' && typeof window.setup.syncPlayerAliases === 'function') { window.setup.syncPlayerAliases(); } else if (!playerState && typeof State !== 'undefined' && State.variables) { State.variables.playerResources = container; } } function getStoredMarketSnapshot() { const playerState = getPlayerState(); if (!playerState || !playerState.market || typeof playerState.market !== 'object') { return null; } return playerState.market; } function hasHydratableMarketData(snapshot) { if (!snapshot || typeof snapshot !== 'object') { return false; } if (Array.isArray(snapshot.citizenQueue) && snapshot.citizenQueue.length > 0) { return true; } if (Array.isArray(snapshot.demandFeed) && snapshot.demandFeed.length > 0) { return true; } if (Array.isArray(snapshot.demandSignals) && snapshot.demandSignals.length > 0) { return true; } if (Array.isArray(snapshot.supplyRecords) && snapshot.supplyRecords.length > 0) { return true; } if (Array.isArray(snapshot.purchaseHistory) && snapshot.purchaseHistory.length > 0) { return true; } if (Array.isArray(snapshot.reports) && snapshot.reports.length > 0) { return true; } if (snapshot.hubs && typeof snapshot.hubs === 'object') { for (const key in snapshot.hubs) { if (!Object.prototype.hasOwnProperty.call(snapshot.hubs, key)) { continue; } const hub = snapshot.hubs[key]; if (!hub || typeof hub !== 'object') { continue; } if (Array.isArray(hub.listings) && hub.listings.length > 0) { return true; } if (Array.isArray(hub.reports) && hub.reports.length > 0) { return true; } if (Array.isArray(hub.trendData) && hub.trendData.length > 0) { return true; } } } return false; } function persistMarketState() { if (typeof State === 'undefined' || !State.variables || !State.variables.marketData || typeof State.variables.marketData !== 'object') { return; } const playerState = getPlayerState(); if (!playerState) { return; } playerState.market = State.variables.marketData; } function hydrateMarketStateFromPlayer(options = {}) { if (typeof State === 'undefined' || !State.variables) { return false; } const force = options.force === true; if (!force && !State.variables.__marketNeedsHydrate) { return false; } const snapshot = getStoredMarketSnapshot(); if (!snapshot || !hasHydratableMarketData(snapshot)) { if (force) { State.variables.__marketNeedsHydrate = true; } return false; } State.variables.marketData = snapshot; if (State.variables.__marketNeedsHydrate !== undefined) { delete State.variables.__marketNeedsHydrate; } persistMarketState(); return true; } const DEFAULT_LISTING_TEMPLATE_HTML = ` <div class="market-listing-card" data-item-id="" data-owner=""> <div class="listing-thumb"></div> <div class="listing-body"> <div class="listing-header"> <div class="listing-name">Item Name</div> <div class="listing-owner">Seller</div> </div> <div class="listing-stats"> <span class="listing-quantity">x0</span> <span class="listing-price">0g</span> <span class="listing-demand-badge neutral">Demand</span> </div> </div> <button class="listing-action">Buy</button> </div> `; function clamp(value, min, max) { if (value < min) return min; if (value > max) return max; return value; } function urgencyWeight(level) { if (level === 'critical') return 3; if (level === 'high') return 2; if (level === 'low') return 0.5; return 1; } function trimLog(list, limit) { if (!Array.isArray(list)) return; if (list.length > limit) { list.splice(0, list.length - limit); } } function resolveHubId(requested) { if (requested) return requested; if (runtime.activeHubId) return runtime.activeHubId; const state = getState(); if (state && state.marketActiveHubId) return state.marketActiveHubId; return 'town_square_market'; } function getResourceKeyFromListing(listing) { if (!listing) return null; if (listing.meta && listing.meta.resourceKey) { return listing.meta.resourceKey; } if (listing.itemId) { const mapped = resolveResourceKeyFromItemId(listing.itemId); if (mapped) { return mapped; } return listing.itemId; } return null; } function ensureState() { if (typeof State === 'undefined' || !State.variables) { return null; } let mutated = false; if (!State.variables.marketData || typeof State.variables.marketData !== 'object') { const snapshot = getStoredMarketSnapshot(); if (snapshot) { State.variables.marketData = snapshot; mutated = true; } else { State.variables.marketData = { version: 2, hubs: {}, citizenQueue: [], reports: [], demandSignals: [], supplyRecords: [], purchaseHistory: [], demandFeed: [] }; State.variables.__marketNeedsHydrate = true; mutated = true; } } else if (hydrateMarketStateFromPlayer()) { mutated = true; } if (!Array.isArray(State.variables.marketData.demandSignals)) { State.variables.marketData.demandSignals = []; mutated = true; } if (!Array.isArray(State.variables.marketData.supplyRecords)) { State.variables.marketData.supplyRecords = []; mutated = true; } if (!Array.isArray(State.variables.marketData.purchaseHistory)) { State.variables.marketData.purchaseHistory = []; mutated = true; } if (!Array.isArray(State.variables.marketData.demandFeed)) { State.variables.marketData.demandFeed = []; mutated = true; } if (!State.variables.marketActiveHubId) { State.variables.marketActiveHubId = 'town_square_market'; mutated = true; } if (mutated) { persistMarketState(); } return State.variables.marketData; } function getState() { return ensureState(); } function deepMerge(target, source) { if (!source) return target; Object.keys(source).forEach(key => { const value = source[key]; if (Array.isArray(value)) { target[key] = Array.isArray(target[key]) ? target[key] : []; if (target[key].length === 0) { target[key] = value.slice(); } } else if (value && typeof value === 'object') { target[key] = target[key] && typeof target[key] === 'object' ? target[key] : {}; deepMerge(target[key], value); } else if (value !== undefined) { target[key] = value; } }); return target; } function ensureHub(hubId, config) { const state = getState(); if (!state) return null; let mutated = false; if (!state.hubs[hubId]) { state.hubs[hubId] = JSON.parse(JSON.stringify(DEFAULT_HUB)); mutated = true; } if (config) { deepMerge(state.hubs[hubId], config); mutated = true; } if (!Array.isArray(state.hubs[hubId].listings)) { state.hubs[hubId].listings = []; mutated = true; } if (!state.hubs[hubId].demandMetrics || typeof state.hubs[hubId].demandMetrics !== 'object') { state.hubs[hubId].demandMetrics = {}; mutated = true; } if (!Array.isArray(state.hubs[hubId].priceHistory)) { state.hubs[hubId].priceHistory = []; mutated = true; } if (typeof state.hubs[hubId].turnoverDaily !== 'number') { state.hubs[hubId].turnoverDaily = 0; mutated = true; } if (!Array.isArray(state.hubs[hubId].trendData)) { state.hubs[hubId].trendData = []; mutated = true; } if (mutated) { persistMarketState(); } return state.hubs[hubId]; } function registerHub(hubId, config) { const hub = ensureHub(hubId, config); if (!hub) return null; if (!hub.displayName) { hub.displayName = config && config.name ? config.name : hub.name; } if (!hub.hubId) { hub.hubId = hubId; } if (!hub.capacity) { hub.capacity = DEFAULT_HUB.capacity; } return hub; } function generateListingId() { return `lst_${Date.now()}_${Math.floor(Math.random() * 100000)}`; } function createListing(options) { options = options || {}; const quantity = Math.max(1, options.quantity || 1); const now = Date.now(); const providedPrice = Number(options.price); const baseUnitPrice = options.baseUnitPrice !== undefined ? Math.max(1, Number(options.baseUnitPrice) || 1) : Math.max(1, Math.round((providedPrice || 0) / quantity) || 1); const pricingMode = options.pricingMode || 'dynamic'; const initialPrice = providedPrice && providedPrice > 0 ? providedPrice : baseUnitPrice * quantity; const unitPrice = Math.max(1, Math.round(initialPrice / quantity)); return { id: generateListingId(), itemId: options.itemId, label: options.label || options.itemId, quantity, price: initialPrice, ownerType: options.ownerType || 'citizen', ownerId: options.ownerId || null, ownerName: options.ownerName || null, demandScore: options.demandScore || 100, urgency: options.urgency || 'normal', expiresOn: options.expiresOn || null, createdAt: now, allowNegotiation: options.allowNegotiation ?? false, meta: options.meta || {}, pricingMode, baseUnitPrice, unitPrice, lastPriceUpdate: now }; } function getHubListingSummary(hubId) { const state = getState(); if (!state) return null; const resolvedHubId = hubId || runtime.activeHubId || state.marketActiveHubId || 'town_square_market'; const hub = ensureHub(resolvedHubId); if (!hub) return null; const queueLength = Array.isArray(state.citizenQueue) ? state.citizenQueue.length : 0; const listings = Array.isArray(hub.listings) ? hub.listings : []; return { hubId: resolvedHubId, totalListings: listings.length, listings: listings.map(entry => ({ id: entry.id, itemId: entry.itemId, label: entry.label, quantity: entry.quantity, ownerType: entry.ownerType, ownerName: entry.ownerName, demandScore: entry.demandScore, createdAt: entry.createdAt })), queueLength }; } function debugSimulateCitizenListings(resourceKey, citizenShareAmount, options = {}) { const state = getState(); if (!state) return null; const hubId = options.hubId || runtime.activeHubId || state.marketActiveHubId || 'town_square_market'; const amount = Number(citizenShareAmount) || 0; const taxSplit = { citizens: { [resourceKey]: amount }, government: {} }; onDailyProduction(null, taxSplit); return getHubListingSummary(hubId); } function debugEstimateCitizenListingTimeline(resourceKey, dailyProduction, citizenTaxRate = 0.5, days = 7) { const resources = getCatalogResources(); if (!resources || !resources[resourceKey]) return null; const config = getItemConfig(resourceKey); const totalDays = Math.max(1, Math.floor(days)); const production = Number(dailyProduction) || 0; const taxRate = Math.max(0, Math.min(1, Number(citizenTaxRate))); const timeline = []; for (let day = 1; day <= totalDays; day++) { const citizenShare = Math.floor(production * taxRate); const portion = Math.floor(citizenShare * 0.35); timeline.push({ day, citizenShare, portionToMarket: portion, listingPossible: portion > 0 }); } return { resourceKey, stackSize: config.stackSize || 1, timeline }; } function queueCitizenListing(citizenId, listingOptions) { const state = getState(); if (!state) return null; const listing = createListing({ ...listingOptions, ownerType: 'citizen', ownerId: citizenId, pricingMode: listingOptions && listingOptions.pricingMode ? listingOptions.pricingMode : 'dynamic' }); state.citizenQueue.push(listing); persistMarketState(); return listing; } function pullFromCitizenQueue(hub, slotsAvailable) { if (slotsAvailable <= 0) return; const state = getState(); if (!state || !Array.isArray(state.citizenQueue)) return; const remainingQueue = []; state.citizenQueue.forEach(listing => { const preferredHub = listing.meta && listing.meta.preferredHub; const hubMatches = !preferredHub || preferredHub === hub.hubId; if (slotsAvailable > 0 && hubMatches) { hub.listings.push(listing); slotsAvailable--; listing.pricingMode = listing.pricingMode || 'dynamic'; if (!listing.baseUnitPrice) { listing.baseUnitPrice = Math.max(1, Math.round((listing.price || 1) / Math.max(1, listing.quantity || 1))); } if (listing.pricingMode !== 'manual') { const metrics = hub.demandMetrics || {}; const dynamic = computeDynamicPrice(getResourceKeyFromListing(listing), listing.quantity || 1, listing.baseUnitPrice, metrics); listing.price = dynamic.price; listing.unitPrice = dynamic.unitPrice; listing.lastPriceUpdate = Date.now(); } recordSupplyEvent(getResourceKeyFromListing(listing), { hubId: hub.hubId, quantity: listing.quantity, listingCount: 1, ownerType: listing.ownerType }); } else { remainingQueue.push(listing); } }); state.citizenQueue = remainingQueue; persistMarketState(); } function recordDemandSignal(resourceKey, data = {}) { const state = getState(); if (!state || !resourceKey) return; const hubId = resolveHubId(data.hubId); const entry = { id: `demand_${Date.now()}_${Math.floor(Math.random() * 1000)}`, resourceKey, quantity: Math.max(1, Number(data.quantity) || 1), urgency: data.urgency || 'normal', citizenId: data.citizenId || null, citizenName: data.citizenName || null, hubId, timestamp: Date.now() }; state.demandSignals.push(entry); state.demandFeed.push(entry); trimLog(state.demandSignals, DEMAND_SIGNAL_LIMIT); trimLog(state.demandFeed, 50); recalculateDemandScores(hubId); persistMarketState(); } function recordSupplyEvent(resourceKey, data = {}) { const state = getState(); if (!state) return; const hubId = resolveHubId(data.hubId); state.supplyRecords.push({ resourceKey, quantity: Math.max(0, Number(data.quantity) || 0), listingCount: Math.max(1, Number(data.listingCount) || 1), ownerType: data.ownerType || 'citizen', hubId, timestamp: Date.now() }); trimLog(state.supplyRecords, SUPPLY_RECORD_LIMIT); recalculateDemandScores(hubId); persistMarketState(); } function recordDemandFulfillment(resourceKey, quantity, hubId) { const state = getState(); if (!state || !resourceKey) return; const entry = { resourceKey, quantity: Math.max(1, Number(quantity) || 1), hubId: resolveHubId(hubId), timestamp: Date.now() }; state.purchaseHistory.push(entry); trimLog(state.purchaseHistory, SUPPLY_RECORD_LIMIT); recalculateDemandScores(entry.hubId); persistMarketState(); } function computeDynamicPrice(resourceKey, quantity, baseUnitPrice, metrics) { const base = Math.max(1, Number(baseUnitPrice) || 1); const qty = Math.max(1, Number(quantity) || 1); const resourceMetrics = metrics && resourceKey ? metrics[resourceKey] : null; let modifier = 0; if (resourceMetrics) { if (resourceMetrics.demandRatio) { modifier += clamp((resourceMetrics.demandRatio - 1) * 0.35, PRICE_MIN_MODIFIER, PRICE_MAX_MODIFIER); } if (resourceMetrics.avgUrgency) { modifier += clamp((resourceMetrics.avgUrgency - 1) * 0.15, 0, 0.3); } if (resourceMetrics.agePenalty) { modifier += resourceMetrics.agePenalty; } } modifier = clamp(modifier, PRICE_MIN_MODIFIER, PRICE_MAX_MODIFIER); const unitPrice = Math.max(1, Math.round(base * (1 + modifier))); const totalPrice = Math.max(1, unitPrice * qty); return { price: totalPrice, unitPrice, modifier }; } function buildMarketSummary(hub) { if (!hub) return null; const listings = Array.isArray(hub.listings) ? hub.listings : []; const totalDemand = listings.reduce((sum, entry) => sum + (entry.demandScore || 0), 0); const avgDemand = listings.length ? Math.round(totalDemand / listings.length) : 0; const turnover = Math.round(hub.turnoverDaily || 0); return { hubName: hub.displayName || hub.name, totalListings: listings.length, avgDemand, turnover }; } function recalculateDemandScores(hubId) { const state = getState(); if (!state) return null; const resolvedHubId = resolveHubId(hubId); const hub = ensureHub(resolvedHubId); if (!hub) return null; const now = Date.now(); const windowStart = now - DEMAND_WINDOW_MS; state.demandSignals = state.demandSignals.filter(entry => entry.timestamp >= windowStart); state.supplyRecords = state.supplyRecords.filter(entry => entry.timestamp >= windowStart); state.purchaseHistory = state.purchaseHistory.filter(entry => entry.timestamp >= windowStart); const metrics = {}; state.demandSignals.forEach(entry => { if (entry.hubId !== resolvedHubId) return; const key = entry.resourceKey; if (!key) return; if (!metrics[key]) { metrics[key] = { requests: 0, urgencySum: 0, supply: 0, fulfilled: 0, listingCount: 0, avgUrgency: 1, demandRatio: 1, agePenalty: 0 }; } metrics[key].requests += entry.quantity || 1; metrics[key].urgencySum += urgencyWeight(entry.urgency); }); state.supplyRecords.forEach(entry => { if (entry.hubId !== resolvedHubId) return; const key = entry.resourceKey; if (!key) return; if (!metrics[key]) { metrics[key] = { requests: 0, urgencySum: 0, supply: 0, fulfilled: 0, listingCount: 0, avgUrgency: 1, demandRatio: 1, agePenalty: 0 }; } metrics[key].supply += entry.quantity || 0; metrics[key].listingCount += Math.max(0, entry.listingCount || 0); }); state.purchaseHistory.forEach(entry => { if (entry.hubId !== resolvedHubId) return; const key = entry.resourceKey; if (!key) return; if (!metrics[key]) { metrics[key] = { requests: 0, urgencySum: 0, supply: 0, fulfilled: 0, listingCount: 0, avgUrgency: 1, demandRatio: 1, agePenalty: 0 }; } metrics[key].fulfilled += entry.quantity || 0; }); const listingAges = {}; if (Array.isArray(hub.listings)) { hub.listings.forEach(listing => { const key = getResourceKeyFromListing(listing); if (!key) return; if (!listingAges[key]) { listingAges[key] = { total: 0, count: 0 }; } const ageHours = (now - (listing.createdAt || now)) / 3600000; listingAges[key].total += ageHours; listingAges[key].count += 1; }); } Object.entries(metrics).forEach(([key, value]) => { const requestCount = value.requests || 0; value.avgUrgency = requestCount > 0 ? value.urgencySum / requestCount : 1; const supplyVolume = value.supply || 0; const fulfilledVolume = value.fulfilled || 0; const netRequests = Math.max(0, requestCount - fulfilledVolume); const denominator = Math.max(1, supplyVolume + fulfilledVolume * 0.5); value.demandRatio = (netRequests + 1) / (denominator + 1); const ageInfo = listingAges[key]; if (ageInfo && ageInfo.count > 0) { const avgAge = ageInfo.total / ageInfo.count; value.agePenalty = -clamp(avgAge * PRICE_DECAY_PER_HOUR, 0, 0.25); value.listingCount += ageInfo.count; } else { value.agePenalty = 0; } }); hub.demandMetrics = metrics; hub.trendData = Object.entries(metrics).map(([key, value]) => ({ resourceKey: key, demandRatio: value.demandRatio, avgUrgency: value.avgUrgency, agePenalty: value.agePenalty })).sort((a, b) => (b.demandRatio - a.demandRatio)); runtime.summary = buildMarketSummary(hub); runtime.demandFeed = state.demandFeed.slice(-10).reverse(); persistMarketState(); return metrics; } function updateHubPricing(hub) { if (!hub) return; const metrics = hub.demandMetrics || {}; const now = Date.now(); if (!Array.isArray(hub.listings)) return; hub.listings.forEach(listing => { if (listing.pricingMode === 'manual') { listing.unitPrice = Math.max(1, Math.round((listing.price || 0) / Math.max(1, listing.quantity || 1))); return; } const resourceKey = getResourceKeyFromListing(listing); const baseUnitPrice = listing.baseUnitPrice || Math.max(1, Math.round((listing.price || 1) / Math.max(1, listing.quantity || 1))); const result = computeDynamicPrice(resourceKey, listing.quantity || 1, baseUnitPrice, metrics); listing.price = result.price; listing.unitPrice = result.unitPrice; listing.lastPriceUpdate = now; listing.baseUnitPrice = baseUnitPrice; }); } function renderSummaryPanels(hub) { const summary = runtime.summary || buildMarketSummary(hub); const totalEl = document.getElementById('market-total-listings'); if (totalEl) { totalEl.textContent = summary ? summary.totalListings : 0; } const avgEl = document.getElementById('market-avg-demand'); if (avgEl) { avgEl.textContent = summary ? summary.avgDemand : 0; } const turnoverEl = document.getElementById('market-daily-turnover'); if (turnoverEl) { turnoverEl.textContent = summary ? `${summary.turnover}g` : '0g'; } } function renderTrendList(hub) { const listEl = document.getElementById('market-trend-list'); if (!listEl) return; listEl.innerHTML = ''; const trendData = Array.isArray(hub && hub.trendData) ? hub.trendData.slice(0, 3) : []; if (!trendData.length) { const empty = document.createElement('div'); empty.className = 'trend-empty'; empty.textContent = 'No trend data yet.'; listEl.appendChild(empty); return; } trendData.forEach(entry => { const row = document.createElement('div'); row.className = 'trend-row'; const name = document.createElement('span'); name.className = 'trend-name'; const config = getItemConfig(entry.resourceKey); name.textContent = config && config.label ? config.label : entry.resourceKey; const demand = document.createElement('span'); demand.className = 'trend-demand'; if (entry.demandRatio >= 1.2) { demand.classList.add('high'); demand.textContent = 'High Demand'; } else if (entry.demandRatio <= 0.8) { demand.classList.add('low'); demand.textContent = 'Low Demand'; } else { demand.classList.add('balanced'); demand.textContent = 'Stable'; } const price = document.createElement('span'); price.className = 'trend-price'; if (entry.demandRatio >= 1.2) { price.classList.add('up'); price.textContent = `+${Math.round((entry.demandRatio - 1) * 100)}%`; } else if (entry.demandRatio <= 0.8) { price.classList.add('down'); price.textContent = `${Math.round((entry.demandRatio - 1) * 100)}%`; } else { price.classList.add('flat'); price.textContent = '0%'; } row.appendChild(name); row.appendChild(demand); row.appendChild(price); listEl.appendChild(row); }); } function renderDemandFeed() { const listEl = document.getElementById('market-demand-feed-list'); if (!listEl) return; listEl.innerHTML = ''; const feed = runtime.demandFeed || []; if (!feed.length) { const empty = document.createElement('div'); empty.className = 'demand-feed-empty'; empty.textContent = 'No active requests.'; listEl.appendChild(empty); return; } feed.forEach(entry => { const row = document.createElement('div'); row.className = 'demand-feed-row'; const name = document.createElement('span'); name.className = 'demand-feed-citizen'; name.textContent = entry.citizenName || 'Citizen'; const detail = document.createElement('span'); detail.className = 'demand-feed-detail'; const config = getItemConfig(entry.resourceKey); const label = config && config.label ? config.label : entry.resourceKey; detail.textContent = `${label} x${entry.quantity}`; const urgency = document.createElement('span'); urgency.className = 'demand-feed-urgency'; urgency.textContent = entry.urgency || 'normal'; if (entry.urgency === 'high' || entry.urgency === 'critical') { urgency.classList.add('high'); } else if (entry.urgency === 'low') { urgency.classList.add('low'); } row.appendChild(name); row.appendChild(detail); row.appendChild(urgency); listEl.appendChild(row); }); } function populateSellForm() { const select = document.getElementById('sell-item-select'); const quantityInput = document.getElementById('sell-item-quantity'); if (!select) return; select.innerHTML = ''; runtime.inventorySellCache = {}; let inventoryItems = []; if (window.PlayerInventorySystem && typeof window.PlayerInventorySystem.listSellableItems === 'function') { try { const resolved = window.PlayerInventorySystem.listSellableItems({ excludeReserved: true }); if (Array.isArray(resolved)) { inventoryItems = resolved.slice(); } } catch (error) { inventoryItems = []; } } if (!Array.isArray(inventoryItems) || inventoryItems.length === 0) { select.disabled = true; const placeholder = document.createElement('option'); placeholder.value = ''; placeholder.textContent = 'No sellable items'; select.appendChild(placeholder); if (quantityInput) { quantityInput.value = 1; quantityInput.max = 1; } updateSellPriceHint(); return; } select.disabled = false; inventoryItems.sort((a, b) => { const nameA = (a.name || a.label || a.itemId || '').toString().toLowerCase(); const nameB = (b.name || b.label || b.itemId || '').toString().toLowerCase(); if (nameA < nameB) return -1; if (nameA > nameB) return 1; return 0; }); inventoryItems.forEach(item => { const key = String(item.itemId !== undefined ? item.itemId : item.id); if (!key) return; const resourceKey = resolveResourceKeyFromItemId(key) || key; const available = Math.max(1, Number(item.availableQuantity !== undefined ? item.availableQuantity : item.quantity || 1)); const option = document.createElement('option'); option.value = key; option.textContent = item.name || item.label || key; option.dataset.available = String(available); option.dataset.resourceKey = resourceKey; runtime.inventorySellCache[key] = Object.assign({}, item, { itemId: key, resourceKey, availableQuantity: available }); select.appendChild(option); }); if (!select.dataset.inventoryBind) { select.addEventListener('change', () => { const priceInput = document.getElementById('sell-item-price'); if (priceInput) { delete priceInput.dataset.manual; } updateSellPriceHint(); }); select.dataset.inventoryBind = 'true'; } if (quantityInput) { quantityInput.value = 1; } updateSellPriceHint(); } function updateSellPriceHint() { const select = document.getElementById('sell-item-select'); const hint = document.getElementById('sell-price-hint'); const priceInput = document.getElementById('sell-item-price'); const quantityInput = document.getElementById('sell-item-quantity'); if (!select || !hint) return; const selectedValue = select.disabled ? '' : select.value; const cache = runtime.inventorySellCache || {}; const itemMeta = selectedValue && cache[selectedValue] ? cache[selectedValue] : null; if (!itemMeta) { hint.textContent = ''; if (quantityInput) { quantityInput.max = 1; if (!select.disabled) { quantityInput.value = 1; } } return; } const available = Math.max(1, Number(itemMeta.availableQuantity !== undefined ? itemMeta.availableQuantity : itemMeta.quantity || 1)); if (quantityInput) { quantityInput.max = String(available); let currentValue = Math.max(1, Number(quantityInput.value) || 1); if (currentValue > available) { currentValue = available; quantityInput.value = available; } } const quantity = Math.max(1, Number(quantityInput ? quantityInput.value : 1) || 1); const resourceKey = itemMeta.resourceKey || itemMeta.itemId || selectedValue; let base = 1; if (typeof itemMeta.value === 'number') { base = Math.max(1, Math.round(itemMeta.value)); } const config = resourceKey ? getItemConfig(resourceKey) : null; if (config && typeof config.basePrice === 'number') { base = Math.max(1, config.basePrice); } const hub = getActiveHub(); const metrics = hub && hub.demandMetrics ? hub.demandMetrics : {}; const suggestion = computeDynamicPrice(resourceKey, quantity, base, metrics); const suggestedPrice = suggestion && suggestion.price ? suggestion.price : Math.max(1, Math.round(base * quantity)); hint.textContent = Suggested; if (priceInput && priceInput.dataset.manual !== 'true') { priceInput.value = suggestedPrice; } } function updateSellPriceHint() { const select = document.getElementById('sell-item-select'); const hint = document.getElementById('sell-price-hint'); const priceInput = document.getElementById('sell-item-price'); const quantityInput = document.getElementById('sell-item-quantity'); if (!select || !hint) return; const resourceKey = select.value; const quantity = Math.max(1, Number(quantityInput ? quantityInput.value : 1) || 1); const config = resourceKey ? getItemConfig(resourceKey) : null; const hub = getActiveHub(); const metrics = hub && hub.demandMetrics ? hub.demandMetrics : {}; const base = config && typeof config.basePrice === 'number' ? Math.max(1, config.basePrice) : 1; const suggestion = computeDynamicPrice(resourceKey, quantity, base, metrics); hint.textContent = suggestion ? `Suggested ${suggestion.price}g` : ''; if (priceInput && priceInput.dataset.manual !== 'true') { priceInput.value = suggestion.price; } } function handleSellFormSubmit(event) { if (event) { event.preventDefault(); } const select = document.getElementById('sell-item-select'); const quantityInput = document.getElementById('sell-item-quantity'); const priceInput = document.getElementById('sell-item-price'); if (!select || !quantityInput || !priceInput || select.disabled) return; const selectedValue = select.value; const cache = runtime.inventorySellCache || {}; const itemMeta = selectedValue && cache[selectedValue] ? cache[selectedValue] : null; if (!itemMeta) { if (typeof window.showToast === 'function') { window.showToast('Select an item to sell.', 'error'); } return; } const quantity = Math.max(1, Number(quantityInput.value) || 1); const available = Math.max(1, Number(itemMeta.availableQuantity !== undefined ? itemMeta.availableQuantity : itemMeta.quantity || 1)); if (quantity > available) { quantityInput.value = available; if (typeof window.showToast === 'function') { window.showToast('Not enough quantity available.', 'error'); } return; } const price = Math.max(1, Number(priceInput.value) || 1); const resourceKey = itemMeta.resourceKey || itemMeta.itemId || selectedValue; const label = itemMeta.name || itemMeta.label || resourceKey; const baseUnitPrice = Math.max(1, Math.round(price / quantity)); const listing = createPlayerListing({ hubId: resolveHubId(), resourceKey, itemId: itemMeta.itemId, label, quantity, price, ownerName: 'Player Holdings', baseUnitPrice, pricingMode: 'manual', meta: { resourceKey, source: 'player_listing', preferredHub: resolveHubId(), inventoryItemId: itemMeta.itemId } }); if (!listing) { return; } if (typeof window.showToast === 'function') { window.showToast('Listing posted to market.', 'success'); } quantityInput.value = 1; delete priceInput.dataset.manual; populateSellForm(); } function createPlayerListing(options) { options = options || {}; const hubId = resolveHubId(options.hubId); const hub = ensureHub(hubId); if (!hub) return null; const quantity = Math.max(1, Number(options.quantity) || 1); const price = Math.max(1, Number(options.price) || 1); const listing = createListing({ itemId: options.itemId, label: options.label || options.itemId, quantity, price, ownerType: 'player', ownerName: options.ownerName || 'Player Holdings', baseUnitPrice: options.baseUnitPrice || Math.max(1, Math.round(price / quantity)), pricingMode: options.pricingMode || 'manual', meta: Object.assign({}, options.meta || {}, { resourceKey: options.resourceKey || (options.meta && options.meta.resourceKey) || options.itemId, preferredHub: hubId }) }); listing.price = price; listing.unitPrice = Math.max(1, Math.round(price / quantity)); if (window.PlayerInventorySystem && typeof window.PlayerInventorySystem.reserveInventoryItems === 'function') { const reservation = window.PlayerInventorySystem.reserveInventoryItems({ listingId: listing.id, items: [{ itemId: listing.itemId, quantity }] }); if (!reservation) { if (typeof window.showToast === 'function') { window.showToast('Not enough items in inventory for that listing.', 'error'); } return null; } listing.meta = listing.meta || {}; listing.meta.inventoryReservationToken = reservation.token; listing.meta.inventoryItemId = listing.itemId; listing.meta.inventoryReservedQuantity = quantity; } hub.listings = Array.isArray(hub.listings) ? hub.listings : []; hub.listings.push(listing); recordSupplyEvent(options.resourceKey || getResourceKeyFromListing(listing), { hubId, quantity, ownerType: 'player', listingCount: 1 }); refreshHub(hubId); renderActiveHub(); return listing; } function bindUI() { if (runtime.uiBound) return; const filterGroup = document.getElementById('market-filter-group'); if (filterGroup) { filterGroup.addEventListener('click', event => { const target = event.target; if (!target || !target.matches('button.market-filter')) return; runtime.activeFilter = target.dataset.filter || 'all'; Array.from(filterGroup.querySelectorAll('button.market-filter')).forEach(btn => { btn.classList.toggle('active', btn === target); }); renderActiveHub(); }); } const sortSelect = document.getElementById('market-sort-select'); if (sortSelect) { sortSelect.addEventListener('change', () => { runtime.activeSort = sortSelect.value || 'demand'; renderActiveHub(); }); } const submitButton = document.getElementById('sell-item-submit'); if (submitButton) { submitButton.addEventListener('click', handleSellFormSubmit); } const priceInput = document.getElementById('sell-item-price'); if (priceInput) { priceInput.addEventListener('input', () => { priceInput.dataset.manual = 'true'; }); } const quantityInput = document.getElementById('sell-item-quantity'); if (quantityInput) { quantityInput.addEventListener('input', () => { const priceField = document.getElementById('sell-item-price'); if (priceField) { delete priceField.dataset.manual; } updateSellPriceHint(); }); } runtime.uiBound = true; } function ensureSampleData(hub) { if (hub.sampleSeeded || (hub.listings && hub.listings.length > 0)) { return; } const samples = [ createListing({ itemId: 'grain_sacks', label: 'Sacks of Grain', quantity: 40, price: 120, ownerType: 'citizen', ownerName: 'Eleanor Miller', demandScore: 118, urgency: 'high' }), createListing({ itemId: 'iron_tools', label: 'Iron Tool Set', quantity: 10, price: 85, ownerType: 'trader', ownerName: 'Blackstone Caravan', demandScore: 96 }), createListing({ itemId: 'linen_cloth', label: 'Bolts of Linen Cloth', quantity: 15, price: 140, ownerType: 'player', ownerName: 'Player Stock', demandScore: 132, allowNegotiation: true }) ]; hub.listings = hub.listings || []; samples.forEach(listing => hub.listings.push(listing)); hub.sampleSeeded = true; persistMarketState(); } function createListingsFromResource(hubId, resourceKey, amount, options = {}) { const resources = getCatalogResources(); if (!resources || !resources[resourceKey] || amount <= 0) { return []; } const config = getItemConfig(resourceKey); const hub = ensureHub(hubId); if (!hub) { return []; } const stackSize = options.stackSize || config.stackSize || 10; const batches = Math.floor(amount / stackSize); const remainder = amount % stackSize; const listings = []; const ownerName = options.ownerName || config.ownerName || (config.ownerDefaults && config.ownerDefaults.ownerName) || 'Local Merchants'; const ownerType = options.ownerType || 'citizen'; const baseUnitPrice = Math.max(1, options.basePrice || config.basePrice || 5); const demandBase = options.demandScore || config.demandScore || 110; const hubMetrics = hub.demandMetrics && Object.keys(hub.demandMetrics).length ? hub.demandMetrics : recalculateDemandScores(hubId) || {}; const priority = options.priority ?? config.priority ?? (config.market && config.market.priority) ?? 5; const itemId = config.itemId || resourceKey; const label = config.label || resourceKey; function createBatchListing(quantity) { const dynamicPrice = computeDynamicPrice(resourceKey, quantity, baseUnitPrice, hubMetrics); const listing = createListing({ itemId, label, quantity, price: dynamicPrice.price, ownerType, ownerName, demandScore: demandBase, meta: { resourceKey, source: options.source || 'daily_production', preferredHub: hubId, priority }, baseUnitPrice, pricingMode: 'dynamic' }); listing.unitPrice = dynamicPrice.unitPrice; hub.listings.push(listing); listings.push(listing); recordSupplyEvent(resourceKey, { hubId, quantity, ownerType, listingCount: 1 }); } for (let i = 0; i < batches; i++) { createBatchListing(stackSize); } if (remainder > 0) { createBatchListing(remainder); } return listings; } function logDailyMarketReport(hubId, entries) { const state = getState(); if (!state) return; const hub = ensureHub(hubId); if (!hub) return; hub.reports = hub.reports || []; const reportEntry = { timestamp: Date.now(), type: 'daily_supply', entries }; hub.reports.push(reportEntry); state.reports.push({ hubId, ...reportEntry }); persistMarketState(); } function onDailyProduction(totalProduction, taxSplit) { const state = getState(); if (!state) return; const hubId = state.marketActiveHubId || runtime.activeHubId || 'town_square_market'; const hub = ensureHub(hubId); if (!hub) return; const citizenShare = (taxSplit && taxSplit.citizens) ? taxSplit.citizens : {}; const listingsCreated = []; Object.entries(citizenShare).forEach(([resourceKey, amount]) => { const resources = getCatalogResources(); if (!resources || !resources[resourceKey] || amount <= 0) { return; } const config = getItemConfig(resourceKey); const portion = Math.floor(amount * 0.35); if (portion <= 0) return; let createdSummaries = []; if (window.PopulationSystem && typeof window.PopulationSystem.offerSurplusToMarket === 'function') { createdSummaries = window.PopulationSystem.offerSurplusToMarket(resourceKey, portion, { hubId, listingConfig: config }) || []; } if (!createdSummaries.length) { const fallbackListings = createListingsFromResource(hubId, resourceKey, portion, { ownerName: config.ownerName || (config.ownerDefaults && config.ownerDefaults.ownerName) || 'Local Merchants', ownerType: 'citizen', source: 'daily_production' }); createdSummaries = fallbackListings.map(listing => ({ listingId: listing.id, resource: resourceKey, quantity: listing.quantity, price: listing.price, owner: listing.ownerName })); } listingsCreated.push(...createdSummaries); }); if (listingsCreated.length > 0) { logDailyMarketReport(hubId, listingsCreated); refreshHub(hubId); renderActiveHub(); } } function setActiveHub(hubId, options) { const state = getState(); if (!state) return; const hub = ensureHub(hubId, options); if (!hub) return; runtime.activeHubId = hubId; state.marketActiveHubId = hubId; ensureSampleData(hub); refreshHub(hubId); renderActiveHub(); } function getActiveHub() { const state = getState(); if (!state) return null; const hubId = runtime.activeHubId || state.marketActiveHubId || 'town_square_market'; return ensureHub(hubId); } function scheduleRenderRetry(delay = RENDER_RETRY_DELAY) { if (!runtime.renderRetry) { runtime.renderRetry = { timer: null, attempts: 0 }; } if (runtime.renderRetry.attempts >= RENDER_RETRY_LIMIT) { return; } if (runtime.renderRetry.timer) { return; } runtime.renderRetry.timer = setTimeout(() => { runtime.renderRetry.timer = null; runtime.renderRetry.attempts += 1; renderActiveHub(); }, delay); } function resetRenderRetry() { if (!runtime.renderRetry) { runtime.renderRetry = { timer: null, attempts: 0 }; } if (runtime.renderRetry.timer) { clearTimeout(runtime.renderRetry.timer); runtime.renderRetry.timer = null; } runtime.renderRetry.attempts = 0; } function ensureListingTemplate(container) { if (!container) { return null; } if (runtime.listingTemplateSource && typeof runtime.listingTemplateSource.cloneNode === 'function') { return runtime.listingTemplateSource; } let template = document.getElementById('market-listing-template'); const resolveSource = node => { if (!node) return null; if (node.content && node.content.firstElementChild) { return node.content.firstElementChild; } return node.firstElementChild || null; }; let source = resolveSource(template); if (!source || !source.matches || !source.matches('.market-listing-card')) { if (!template) { template = document.createElement('template'); template.id = 'market-listing-template'; if (container.parentNode) { container.parentNode.insertBefore(template, container.nextSibling); } else { document.body.appendChild(template); } } if (!template.innerHTML || template.innerHTML.indexOf('market-listing-card') === -1) { template.innerHTML = DEFAULT_LISTING_TEMPLATE_HTML.trim(); } source = resolveSource(template); } if (!source) { const tempTemplate = document.createElement('template'); tempTemplate.innerHTML = DEFAULT_LISTING_TEMPLATE_HTML.trim(); source = tempTemplate.content.firstElementChild; } runtime.listingTemplateSource = source ? source.cloneNode(true) : null; return runtime.listingTemplateSource; } function buildListingDescription(listing) { if (!listing) return ''; const parts = []; const meta = listing.meta || {}; if (meta.description) { parts.push(String(meta.description)); } const details = []; if (listing.quantity !== undefined && listing.quantity !== null) { details.push(`Quantity: ${listing.quantity}`); } if (listing.price !== undefined && listing.price !== null) { details.push(`Price: ${listing.price}g`); } if (meta.quality) { details.push(`Quality: ${meta.quality}`); } if (listing.ownerType) { details.push(`Seller Type: ${listing.ownerType}`); } if (details.length) { parts.push(details.join(' | ')); } return parts.join('\n'); } function renderActiveHub() { const hub = getActiveHub(); if (!hub) return; const container = document.getElementById('market-listings'); if (!container) { scheduleRenderRetry(); return; } bindUI(); populateSellForm(); const metrics = recalculateDemandScores(hub.hubId) || hub.demandMetrics || {}; updateHubPricing(hub); renderSummaryPanels(hub); renderTrendList(hub); renderDemandFeed(); const emptyHint = container.querySelector('.market-empty-hint'); const header = document.querySelector('.market-hub-header p'); if (header) { header.textContent = hub.displayName || hub.name; } const templateRootSource = ensureListingTemplate(container); if (!templateRootSource) { scheduleRenderRetry(); return; } resetRenderRetry(); Array.from(container.children).forEach(child => { if (!child.matches('.market-empty-hint')) { child.remove(); } }); let listings = Array.isArray(hub.listings) ? hub.listings.slice() : []; listings = listings.filter(listing => { if (runtime.activeFilter === 'citizen') { return listing.ownerType === 'citizen'; } if (runtime.activeFilter === 'trader') { return listing.ownerType === 'trader'; } if (runtime.activeFilter === 'player') { return listing.ownerType === 'player'; } return true; }); if (runtime.activeSort === 'priceAsc') { listings.sort((a, b) => (a.price || 0) - (b.price || 0)); } else if (runtime.activeSort === 'priceDesc') { listings.sort((a, b) => (b.price || 0) - (a.price || 0)); } else if (runtime.activeSort === 'newest') { listings.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0)); } else { listings.sort((a, b) => (b.demandScore || 0) - (a.demandScore || 0)); } if (listings.length === 0) { if (emptyHint) { emptyHint.style.display = 'block'; } return; } if (emptyHint) { emptyHint.style.display = 'none'; } listings.forEach(listing => { const card = templateRootSource.cloneNode(true); card.dataset.listingId = listing.id; card.dataset.itemId = listing.itemId; card.dataset.ownerType = listing.ownerType; card.classList.add(`owner-${listing.ownerType}`); const nameEl = card.querySelector('.listing-name'); if (nameEl) { nameEl.textContent = listing.label; } const ownerEl = card.querySelector('.listing-owner'); if (ownerEl) { ownerEl.textContent = listing.ownerName || listing.ownerType; } const description = buildListingDescription(listing); if (description) { card.dataset.description = description; card.setAttribute('title', description); } else { delete card.dataset.description; card.removeAttribute('title'); } const quantityEl = card.querySelector('.listing-quantity'); if (quantityEl) { quantityEl.textContent = `x${listing.quantity}`; } const priceEl = card.querySelector('.listing-price'); if (priceEl) { priceEl.textContent = `${listing.price}g`; } const demandBadge = card.querySelector('.listing-demand-badge'); if (demandBadge) { const score = listing.demandScore || 100; demandBadge.classList.remove('high', 'low', 'neutral'); if (score >= 130) { demandBadge.classList.add('high'); demandBadge.textContent = 'High Demand'; } else if (score <= 90) { demandBadge.classList.add('low'); demandBadge.textContent = 'Low Demand'; } else { demandBadge.classList.add('neutral'); demandBadge.textContent = 'Balanced'; } } const thumbEl = card.querySelector('.listing-thumb'); if (thumbEl) { const resourceKey = getResourceKeyFromListing(listing); const config = resourceKey ? getItemConfig(resourceKey) : null; if (config && config.thumbnail) { thumbEl.style.backgroundImage = `url(${config.thumbnail})`; } else { thumbEl.style.backgroundImage = ''; } } const actionBtn = card.querySelector('.listing-action'); if (actionBtn) { actionBtn.textContent = listing.ownerType === 'player' ? 'Put Down Items' : 'Buy'; actionBtn.addEventListener('click', () => { window.MarketSystem.handleListingAction(listing.id); }); } container.appendChild(card); }); } function handleListingAction(listingId, options = {}) { const hub = getActiveHub(); if (!hub) return; const index = hub.listings.findIndex(entry => entry.id === listingId); if (index === -1) return; const listing = hub.listings[index]; if (listing.ownerType === 'player' && !options.forcePurchase) { if (typeof window.showToast === 'function') { window.showToast(`Listing management for ${listing.label} coming soon.`, 'info'); } return; } const quantitySold = listing.quantity || 1; const salePrice = Math.max(0, Number(listing.price) || 0); const { container: playerResources } = getPlayerResourceContainer(); const currentGold = playerResources && typeof playerResources.gold === 'number' ? playerResources.gold : 0; if (salePrice > 0 && listing.ownerType !== 'player') { if (!playerResources || currentGold < salePrice) { if (typeof window.showToast === 'function') { window.showToast('Not enough gold to complete that purchase.', 'error'); } return; } adjustPlayerGold(-salePrice); } const taxSplit = applyMarketTaxes(salePrice); const netToSeller = listing.ownerType === 'player' ? taxSplit.citizens : 0; recordSale(hub.hubId, { listingId: listing.id, itemId: listing.itemId, label: listing.label, quantity: quantitySold, grossGold: salePrice, taxGovernment: taxSplit.government, taxCitizens: taxSplit.citizens, ownerType: listing.ownerType, netToSeller }); recordDemandFulfillment(getResourceKeyFromListing(listing), quantitySold, hub.hubId); if (listing.ownerType === 'player') { const reservationToken = listing.meta && listing.meta.inventoryReservationToken; if (reservationToken && window.PlayerInventorySystem && typeof window.PlayerInventorySystem.commitInventorySale === 'function') { window.PlayerInventorySystem.commitInventorySale(reservationToken, { soldItems: [{ itemId: listing.itemId, quantity: quantitySold }] }); } } hub.turnoverDaily = (hub.turnoverDaily || 0) + salePrice; hub.listings.splice(index, 1); refreshHub(hub.hubId); renderActiveHub(); if (netToSeller > 0) { adjustPlayerGold(netToSeller); if (typeof window.showToast === 'function') { window.showToast(`You earned ${netToSeller} gold from selling ${listing.label}.`, 'success'); } } else if (typeof window.showToast === 'function') { window.showToast(`Purchased ${listing.label} for ${salePrice} gold.`, 'success'); } } function ensureResource(resourceKey) { if (!State.variables.resources) { State.variables.resources = {}; } if (typeof State.variables.resources[resourceKey] !== 'number') { State.variables.resources[resourceKey] = 0; } } function ensureCitizenResource(resourceKey) { if (!State.variables.citizenResources) { State.variables.citizenResources = {}; } if (typeof State.variables.citizenResources[resourceKey] !== 'number') { State.variables.citizenResources[resourceKey] = 0; } } function applyMarketTaxes(grossGold) { if (!grossGold || grossGold <= 0) { return { government: 0, citizens: grossGold || 0 }; } if (typeof window.calculateProductionTaxSplit === 'function') { const split = window.calculateProductionTaxSplit({ gold: grossGold }); if (split && typeof split === 'object') { if (split.government && split.government.gold) { ensureResource('gold'); State.variables.resources.gold += split.government.gold; } if (split.citizens && split.citizens.gold) { ensureCitizenResource('gold'); State.variables.citizenResources.gold += split.citizens.gold; } return { government: split.government ? split.government.gold || 0 : 0, citizens: split.citizens ? split.citizens.gold || 0 : grossGold }; } } return { government: 0, citizens: grossGold }; } function recordSale(hubId, sale) { const state = getState(); if (!state) return; const hub = ensureHub(hubId); if (!hub) return; hub.reports = hub.reports || []; hub.reports.push({ timestamp: Date.now(), ...sale }); state.reports.push({ hubId, ...sale }); persistMarketState(); } function simulateDailySales(hubId) { const hub = ensureHub(hubId); if (!hub || !hub.listings) return; const remainingListings = []; hub.listings.forEach(listing => { const sellChance = listing.ownerType === 'player' ? 0.2 : 0.35; if (Math.random() < sellChance) { const soldQuantity = Math.max(1, Math.floor(listing.quantity * (0.3 + Math.random() * 0.5))); const quantitySold = Math.min(soldQuantity, listing.quantity); const unitPrice = listing.unitPrice || Math.max(1, Math.round((listing.price || 1) / Math.max(1, listing.quantity || 1))); const goldEarned = unitPrice * quantitySold; const taxSplit = applyMarketTaxes(goldEarned); const netToSeller = listing.ownerType === 'player' ? taxSplit.citizens : 0; recordSale(hubId, { listingId: listing.id, itemId: listing.itemId, label: listing.label, quantity: quantitySold, grossGold: goldEarned, taxGovernment: taxSplit.government, taxCitizens: taxSplit.citizens, ownerType: listing.ownerType, netToSeller }); recordDemandFulfillment(getResourceKeyFromListing(listing), quantitySold, hubId); if (listing.ownerType === 'player') { const reservationToken = listing.meta && listing.meta.inventoryReservationToken; if (reservationToken && window.PlayerInventorySystem && typeof window.PlayerInventorySystem.commitInventorySale === 'function') { window.PlayerInventorySystem.commitInventorySale(reservationToken, { soldItems: [{ itemId: listing.itemId, quantity: quantitySold }] }); } } hub.turnoverDaily = (hub.turnoverDaily || 0) + goldEarned; if (netToSeller > 0) { adjustPlayerGold(netToSeller); if (typeof window.showToast === 'function') { window.showToast(`You earned ${netToSeller} gold from selling ${listing.label}.`, 'success'); } } const remainingQuantity = listing.quantity - quantitySold; if (remainingQuantity > 0) { listing.quantity = remainingQuantity; if (listing.meta) { listing.meta.inventoryReservedQuantity = remainingQuantity; } listing.demandScore = Math.max(60, listing.demandScore - 5); listing.price = unitPrice * remainingQuantity; listing.unitPrice = unitPrice; remainingListings.push(listing); } } else { listing.demandScore = Math.min(180, listing.demandScore + 3); remainingListings.push(listing); } }); hub.listings = remainingListings; recalculateDemandScores(hubId); updateHubPricing(hub); persistMarketState(); if (runtime.activeHubId === hubId) { renderActiveHub(); } } function refreshHub(hubId) { const hub = ensureHub(hubId); if (!hub) return; const capacity = hub.capacity || DEFAULT_HUB.capacity; const slotsAvailable = capacity - (hub.listings ? hub.listings.length : 0); if (slotsAvailable > 0) { pullFromCitizenQueue(hub, slotsAvailable); } hub.lastRefresh = Date.now(); recalculateDemandScores(hubId); updateHubPricing(hub); } function bindPassageHook() { if (runtime.passageHookBound) return; const handler = function() { const state = getState(); if (!state) return; const hubId = state.marketActiveHubId || 'town_square_market'; setActiveHub(hubId); }; if (typeof jQuery !== 'undefined' && jQuery(document).on) { jQuery(document).on(':passagerender', function() { const passageName = State && State.passage ? State.passage : ''; if (passageName === 'market_ui' || document.getElementById('market-listings')) { handler(); } }); } else if (document && document.addEventListener) { document.addEventListener('DOMContentLoaded', handler); if (!runtime.domObserver && typeof MutationObserver !== 'undefined') { runtime.domObserver = new MutationObserver(() => { if (document.getElementById('market-listings')) { renderActiveHub(); } }); const target = document.body || document.documentElement; if (target) { runtime.domObserver.observe(target, { childList: true, subtree: true }); } } } runtime.passageHookBound = true; } function initialize() { if (runtime.initialized) return; const state = ensureState(); if (!state) { setTimeout(initialize, 200); return; } getCatalogResources(); registerHub('town_square_market', { displayName: 'Town Square Market', specialization: 'general' }); runtime.initialized = true; bindPassageHook(); } function onBuildingEnter(buildingId, buildingDef, instance) { const context = buildingDef && buildingDef.entryContext ? buildingDef.entryContext : {}; const hubId = context.hubId || 'town_square_market'; const hydrated = hydrateMarketStateFromPlayer({ force: true }); if (hydrated) { persistMarketState(); } registerHub(hubId, { name: buildingDef.name, displayName: buildingDef.name, buildingId: buildingId, areaId: instance && instance.areaId ? instance.areaId : null }); if (typeof State !== 'undefined' && State.variables) { State.variables.marketActiveHubId = hubId; } runtime.activeHubId = hubId; refreshHub(hubId); renderActiveHub(); } function prepareHubContext(context) { if (!context) return; const hubId = context.hubId || 'town_square_market'; const state = getState(); if (!state) return; const hydrated = hydrateMarketStateFromPlayer({ force: true }); if (hydrated) { persistMarketState(); } registerHub(hubId, { displayName: context.displayName || DEFAULT_HUB.name, specialization: context.specialization || 'general' }); state.marketActiveHubId = hubId; runtime.activeHubId = hubId; refreshHub(hubId); renderActiveHub(); } return { initialize, registerHub, ensureHub, getHubListingSummary, debugSimulateCitizenListings, debugEstimateCitizenListingTimeline, queueCitizenListing, createPlayerListing, refreshHub, renderActiveHub, handleListingAction, simulateDailySales, recordSale, recordDemandSignal, recordSupplyEvent, getItemConfig, onBuildingEnter, prepareHubContext, runMarketCycleAfterCombat, setActiveHub, onDailyProduction }; })(); window.MarketSystem.initialize(); window.testMarketDemandProbe = function(hubId) { const resolved = hubId || (typeof State !== 'undefined' && State.variables ? State.variables.marketActiveHubId : null) || 'town_square_market'; const hub = window.MarketSystem.ensureHub(resolved); if (hub) { window.MarketSystem.refreshHub(resolved); } return { summary: window.MarketSystem.getHubListingSummary(resolved), metrics: hub ? hub.demandMetrics : null, feed: (State && State.variables && State.variables.marketData && Array.isArray(State.variables.marketData.demandFeed)) ? State.variables.marketData.demandFeed.slice(-5) : [] }; };
<<script>> (function() { const root = (typeof setup === 'object' && setup) ? setup : (window.setup = window.setup || {}); const combatStatDefinitions = [ { id: "stat_strength", category: "attributes", statKey: "Strength", displayName: "Strength", description: "Raw physical power that drives weapon force and staying power in melee exchanges.", effects: [ { target: "attack", type: "perPoint", value: 0.6 }, { target: "defense", type: "perPoint", value: 0.45 } ] }, { id: "stat_dexterity", category: "attributes", statKey: "Dexterity", displayName: "Dexterity", description: "Agility and precision, improving weapon handling, battlefield positioning, and critical strikes.", effects: [ { target: "attack", type: "perPoint", value: 0.35 }, { target: "movement", type: "perPoint", value: 0.05 }, { target: "critChance", type: "perPointPercent", value: 0.15 } ] }, { id: "stat_stamina", category: "attributes", statKey: "Stamina", displayName: "Stamina", description: "Conditioning and toughness that lets a unit take punishment while staying mobile.", effects: [ { target: "defense", type: "perPoint", value: 0.5 }, { target: "movement", type: "perPoint", value: 0.03 } ] }, { id: "stat_intelligence", category: "attributes", statKey: "Intelligence", displayName: "Intelligence", description: "Tactical insight that translates into smarter targeting and decisive blows.", effects: [ { target: "critChance", type: "perPointPercent", value: 0.1 } ] }, { id: "stat_willpower", category: "attributes", statKey: "Willpower", displayName: "Willpower", description: "Resolve to hold the line, granting steadier defenses and responses under pressure.", effects: [ { target: "defense", type: "perPoint", value: 0.3 } ] }, { id: "skill_melee_combat", category: "skills", statKey: "Melee Combat", displayName: "Melee Combat", description: "Training with blades and close-quarters weapons.", effects: [ { target: "attack", type: "perPoint", value: 1.0 }, { target: "critChance", type: "perPointPercent", value: 0.25 } ] }, { id: "skill_ranged_combat", category: "skills", statKey: "Ranged Combat", displayName: "Ranged Combat", description: "Accuracy with bows, crossbows, and firearms.", effects: [ { target: "attack", type: "perPoint", value: 0.9 }, { target: "critChance", type: "perPointPercent", value: 0.2 } ] }, { id: "skill_acrobatics", category: "skills", statKey: "Acrobatics", displayName: "Acrobatics", description: "Footwork and body control used to reposition and evade.", effects: [ { target: "movement", type: "perPoint", value: 0.08 }, { target: "defense", type: "perPoint", value: 0.1 } ] }, { id: "skill_leadership", category: "skills", statKey: "Leadership", displayName: "Leadership", description: "Battlefield command presence that steadies allies and sharpens strikes.", effects: [ { target: "attack", type: "perPoint", value: 0.3 }, { target: "defense", type: "perPoint", value: 0.25 } ] } ]; root.combatStatDefinitions = combatStatDefinitions; root.getCombatStatDefinition = function(statKey) { if (!statKey) { return null; } return combatStatDefinitions.find(def => def.statKey === statKey) || null; }; root.listCombatStatDefinitions = function() { return combatStatDefinitions.slice(); }; })(); <</script>>
<<run setup.worldRaceLimbs = { "human": { limbs: { head: { tags: ["senses"], defaultStatus: "healthy" }, torso: { tags: ["core"], defaultStatus: "healthy" }, leftArm: { tags: ["grasp"], defaultStatus: "healthy" }, rightArm: { tags: ["grasp", "dominant"], defaultStatus: "healthy" }, leftLeg: { tags: ["movement"], defaultStatus: "healthy" }, rightLeg: { tags: ["movement"], defaultStatus: "healthy" } }, dominantHandFallbacks: { impaired: ["Improvised Strike"], disabled: ["Improvised Kick"] }, statusImpacts: { injured: { limbs: { rightArm: { tagsAdded: ["bleeding"], statMods: { attack_flat: -2 }, blocksSkillTags: ["requiresFineControl"] } } }, maimed: { limbs: { rightArm: { statMods: { attack_flat: -4, critChance_flat: -0.05 }, blocksSkillTags: ["requiresDominantHand", "requiresTwoHands", "ranged"] } } }, missing: { limbs: { rightArm: { statMods: { attack_flat: -6 }, blocksSkillTags: ["requiresDominantHand", "requiresTwoHands", "ranged"], forcedFallbacks: true } } } } } };>>
<div class="fullscreenbackground" id="quest-board-root"> <div class="quest-board-background"> <div class="quest-board-heading"> <button id="quest-board-prev-btn">← Previous</button> <h2 class="quest-board-heading-title">Quest Board</h2> <button id="quest-board-next-btn">Next →</button> <div class="quest-board-heading-controls"> <button id="quest-board-return-btn">Return</button> </div> </div> <div class="quest-board-surface"> <div class="quest-board-notes" id="quest-board-notes-container"> </div> <div class="quest-detail-popup-placeholder" id="quest-modal-container"></div> </div> </div> </div> <<script>> $(document).ready(function() { if (window.QuestBoardSystem && typeof window.QuestBoardSystem.initialize === 'function') { const initialized = window.QuestBoardSystem.initialize('quest-board-notes-container'); if (!initialized) { console.error('[QuestBoard] Failed to initialize quest board system'); } } else { console.error('[QuestBoard] QuestBoardSystem not available - ensure quest-board-system.js is loaded'); } }); <</script>>
<<if State.variables.claimedMaps && State.variables.claimedMaps[State.variables.currentMapId] && State.variables.enterSettlement>> <<set State.variables.currentArea = State.variables.currentMapId>> <<set State.variables.enterSettlement = false>> <<goto "area1">> <</if>> <div class="fullscreenbackground"> <div class="gui_gametab_cb"> <div class="right_container"> <<include "gui-button">> </div> <div class="left_container"> <div class="basic_information"> <div class="character_icon" onclick="openUIScreen('player')"> <img src="Bin/Contents/Characters/Human_Storyline/Story/Player/Frame.png" alt="human_avatar"> </div> <<include "characterstatusgui">> </div> <div class="left_container_buttons"> <button class="left_container_button" data-gui-button="settings" data-tooltip="Settings" data-tooltip-placement="top" onclick="openSettings()"> <img src="Bin/Contents/UI/Buttons/sbtn_settings.png" alt="settings"> </button> <button class="left_container_button" data-gui-button="save" data-tooltip="Save" data-tooltip-placement="top" onclick="saveGame()"> <img src="Bin/Contents/UI/Buttons/sbtn_save.png" alt="save"> </button> <button class="left_container_button" data-gui-button="skip-time" data-tooltip="Skip 30 minutes" data-tooltip-placement="top" onclick="window.useSkipTime(); return false;"> <img src="Bin/Contents/UI/Buttons/sbtn_skiptime.png" alt="time_skip"> </button> <div class="time_and_money"> <div class="dynamic_time_money"> <span class="time_value">12:00 AM | Monday</span> </div> </div> <div class="return_home"> <button class="left_container_button" data-gui-button="return-home" data-tooltip-id="return-home" data-tooltip-placement="top" onclick="if (window.CompassNavigation && typeof window.CompassNavigation.returnToBlackmoorCastle === 'function') { window.CompassNavigation.returnToBlackmoorCastle(); } event.stopPropagation(); return false;"> <img src="Bin/Contents/UI/Buttons/sbtn_rhome.png" alt="return_home"> </button> </div> </div> </div> <div class="middle_container_65px"> <div class="location_name"> <div class="location_text"><p data-map-name></p></div> <<settlementStatus>> </div> </div> <<script>> setTimeout(function() { if (window.disableGameButtons) { window.disableGameButtons(); } }, 0); <</script>> </div> <div class="world-template"> <div class="map-area-mount"></div> <script> (function () { var mapId = window._pendingMapNavigation || null; if (!mapId && typeof State !== "undefined" && State.variables) { mapId = State.variables.currentMapId || null; } if (!mapId) { mapId = "woodlands"; } window._pendingMapNavigation = null; if (typeof State !== "undefined" && State.variables) { State.variables.currentMapId = mapId; } var mount = document.querySelector('.world-template .map-area-mount'); var locationLabel = document.querySelector('.location_text p'); if (window.MapAreaDefinitions && typeof window.MapAreaDefinitions.getMap === "function") { var mapDef = window.MapAreaDefinitions.getMap(mapId); if (mapDef && locationLabel) { locationLabel.textContent = mapDef.name || mapDef.id || ''; } var ambientId = mapDef && mapDef.ambientId ? mapDef.ambientId : 'ambient_forest'; if (window.AudioEvents && typeof window.AudioEvents.emit === 'function') { window.AudioEvents.emit('ambient:set', { id: ambientId }); } else if (window.AudioManager && typeof window.AudioManager.playAmbient === 'function') { window.AudioManager.playAmbient(ambientId); } } else if (locationLabel) { locationLabel.textContent = mapId; } if (mount && window.MapAreaDefinitions && typeof window.MapAreaDefinitions.renderInto === "function") { window.MapAreaDefinitions.renderInto(mount, mapId); if (typeof window.MapAreaDefinitions.restoreRevealedNodes === "function") { setTimeout(function() { var container = document.querySelector('.map-area'); if (container) { window.MapAreaDefinitions.restoreRevealedNodes(container, mapId); } }, 100); } } else if (window.MapAreaRenderer && typeof window.MapAreaRenderer.render === "function") { window.MapAreaRenderer.render(mapId); } if (window.MapAreaDefinitions && typeof window.MapAreaDefinitions.restoreClaimedMapRoutes === "function") { window.MapAreaDefinitions.restoreClaimedMapRoutes(); } if (window.CompassNavigation && typeof window.CompassNavigation.initialize === "function") { window.CompassNavigation.initialize(); if (typeof window.CompassNavigation.ensureMapNode === "function") { window.CompassNavigation.ensureMapNode(mapId); } else if (typeof window.CompassNavigation.setCurrentNode === "function") { window.CompassNavigation.setCurrentNode(mapId); } } setTimeout(function() { var button = document.getElementById('claim-map-button'); if (button) { button.addEventListener('click', function() { if (window.MapAreaDefinitions && window.MapAreaDefinitions.claimMap) { window.MapAreaDefinitions.claimMap(mapId); } }); } }, 100); })(); </script> </div> </div> <<if State.variables.claimedMaps && State.variables.claimedMaps[State.variables.currentMapId]>> <script> (function() { var button = document.getElementById('enter-settlement-button'); if (button) { button.addEventListener('click', function() { if (State && State.variables) { State.variables.enterSettlement = true; State.variables.currentArea = State.variables.currentMapId; State.variables.currentMapId = null; if (window.AudioEvents && typeof window.AudioEvents.emit === 'function') { window.AudioEvents.emit('ambient:set', { id: 'ambient_castle' }); } else if (window.AudioManager && typeof window.AudioManager.playAmbient === 'function') { window.AudioManager.playAmbient('ambient_castle'); } if (typeof Engine !== 'undefined' && Engine.play) { Engine.play('area1'); } } }); } })(); </script> <</if>>
<<if setup && setup.questBoardModal && setup.questBoardModal.isOpen>> <<set _detailQuest = (typeof setup.getQuestDetailData === 'function') ? setup.getQuestDetailData() : null>> <<set _detailMeta = (_detailQuest && _detailQuest.metadata) ? _detailQuest.metadata : {}>> <<set _detailRewards = (_detailQuest && Array.isArray(_detailQuest.rewards)) ? _detailQuest.rewards : ((Array.isArray(_detailMeta.rewards) ? _detailMeta.rewards : []))>> <<set _detailDanger = (_detailQuest && _detailQuest.dangerLevel) ? _detailQuest.dangerLevel : ((_detailMeta && _detailMeta.danger) ? _detailMeta.danger : '--')>> <<set _detailIssuer = (_detailQuest && _detailQuest.giverName) ? _detailQuest.giverName : ((_detailMeta && _detailMeta.citizenName) ? _detailMeta.citizenName : 'Citizen Request')>> <<set _detailStatus = (_detailQuest && _detailQuest.status) ? _detailQuest.status.toString().toLowerCase() : ''>> <<set _detailStatusLabel = (_detailQuest && _detailQuest.status) ? _detailQuest.status.toString().toUpperCase() : 'POSTED'>> <<set _detailSummary = (_detailQuest && _detailQuest.description) ? _detailQuest.description : ((_detailMeta && _detailMeta.summary) ? _detailMeta.summary : '')>> <<set _detailDialogue = (_detailMeta && _detailMeta.dialogue) ? _detailMeta.dialogue : ((_detailQuest && _detailQuest.dialogue) ? _detailQuest.dialogue : '')>> <<set _detailLocation = (_detailMeta && _detailMeta.location) ? _detailMeta.location : ((_detailQuest && _detailQuest.location) ? _detailQuest.location : null)>> <<set _detailStages = (_detailQuest && Array.isArray(_detailQuest.stages)) ? _detailQuest.stages : ((Array.isArray(_detailMeta.stages) ? _detailMeta.stages : []))>> <div id="quest-board-modal-backdrop" class="quest-board-modal-backdrop"></div> <div class="quest-board-modal-card"> <button id="quest-board-modal-close" class="quest-board-modal-close" type="button">×</button> <<if _detailQuest>> <div class="quest-board-modal-header"> <h2 class="quest-board-modal-title"><<print _detailQuest.title || 'Citizen Request'>></h2> <p class="quest-board-modal-issuer">Issued by <<print _detailIssuer>></p> </div> <div class="quest-board-modal-meta"> <span class="quest-board-modal-tag quest-board-modal-tag--danger">Danger: <<print _detailDanger>></span> <<if _detailRewards.length>> <span class="quest-board-modal-tag">Rewards: <<print _detailRewards.join(', ')>></span> <</if>> <<if _detailLocation>> <span class="quest-board-modal-tag">Location: <<print _detailLocation>></span> <</if>> <span class="quest-board-modal-tag quest-board-modal-tag--status">Status: <<print _detailStatusLabel>></span> </div> <<set _detailNoteContent = (setup && typeof setup.renderQuestBoardNoteContent === 'function') ? setup.renderQuestBoardNoteContent(_detailQuest, _detailMeta) : ''>> <<if _detailNoteContent && _detailNoteContent.trim && _detailNoteContent.trim().length>> <div class="quest-board-modal-note-preview"> <<print _detailNoteContent>> </div> <</if>> <div class="quest-board-modal-body"> <<if _detailSummary && _detailSummary.trim().length>> <<set _detailParagraphs = _detailSummary.split(/\n+/)>> <<for _dIndex range 0, _detailParagraphs.length - 1>> <<set _line = _detailParagraphs[_dIndex]>> <<if _line && _line.trim().length>> <p><<print _line.trim()>></p> <</if>> <</for>> <<else>> <p>No additional briefing has been supplied.</p> <</if>> <<if _detailDialogue && _detailDialogue.trim().length>> <div class="quest-board-modal-dialogue"> <<set _dlgLines = _detailDialogue.split(/\n+/)>> <<for _dlgIdx range 0, _dlgLines.length - 1>> <<set _dlg = _dlgLines[_dlgIdx]>> <<if _dlg && _dlg.trim().length>> <p>“<<print _dlg.trim()>>”</p> <</if>> <</for>> </div> <</if>> <<if _detailStages.length>> <div class="quest-board-modal-stages"> <h3>Objectives</h3> <ol> <<for _stageIdx range 0, _detailStages.length - 1>> <<set _stageEntry = _detailStages[_stageIdx]>> <<if _stageEntry && typeof _stageEntry === 'object'>> <li> <<if _stageEntry.title>><strong><<print _stageEntry.title>></strong><</if>> <<if _stageEntry.description>><span><<print _stageEntry.description>></span><</if>> <<elseif _stageEntry.text>><span><<print _stageEntry.text>></span><</if>> </li> <<elseif _stageEntry>> <li><<print _stageEntry>></li> <</if>> <</for>> </ol> </div> <</if>> </div> <div class="quest-board-modal-actions"> <<if _detailStatus === 'posted'>> <button id="quest-board-modal-accept" class="quest-board-modal-btn quest-board-modal-btn--primary" type="button">Accept Quest</button> <</if>> <button id="quest-board-modal-cancel" class="quest-board-modal-btn" type="button">Close</button> </div> <<else>> <div class="quest-board-modal-body"> <p>Quest details are unavailable. Please try another posting.</p> </div> <div class="quest-board-modal-actions"> <button id="quest-board-modal-cancel" class="quest-board-modal-btn" type="button">Close</button> </div> <</if>> </div> <<linkappend "#quest-board-modal-backdrop">> <<run setup.closeQuestDetail()>> <<replace '#quest-board-root'>><<include "actual-quest-board">><</replace>> <</linkappend>> <<linkappend "#quest-board-modal-close">> <<run setup.closeQuestDetail()>> <<replace '#quest-board-root'>><<include "actual-quest-board">><</replace>> <</linkappend>> <<linkappend "#quest-board-modal-cancel">> <<run setup.closeQuestDetail()>> <<replace '#quest-board-root'>><<include "actual-quest-board">><</replace>> <</linkappend>> <<if _detailStatus === 'posted'>> <<linkappend "#quest-board-modal-accept">> <<run setup.acceptQuestFromDetail()>> <<replace '#quest-board-root'>><<include "actual-quest-board">><</replace>> <</linkappend>> <</if>> <</if>>
<<silently>> <<run setup.citizenQuestDefinitions = { "quest_thomas_bandits": { id: "quest_thomas_bandits", type: "citizen_request", title: "Clear the Bandit Camp", description: "Thomas Baker reports that bandits have been harassing travelers near his family's wheat fields. The raiders camp in the nearby woods and threaten the harvest season. He asks for your help in driving them out before they grow bolder.", dangerLevel: "High", encounterId: "bandit_duo_fight", mapId: "woodlands", rewards: [ { type: "gold", amount: 75 }, { type: "influence", amount: 2 } ], citizenId: "citizen_001", dialogue: "My lord, these bandits grow bolder by the day. My wife Eleanor and I fear for our neighbors' safety. Please, help us drive them from our lands.", summary: "Eliminate the bandit duo threatening the Baker family's fields", questAsset: "Bin/Contents/UI/Containers/Paper_1.png" }, "quest_robert_patrol": { id: "quest_robert_patrol", type: "citizen_request", title: "Patrol the Settlement Perimeter", description: "Robert Smith, the settlement's blacksmith and de facto militia leader, needs help patrolling the outer boundaries. Recent tracks suggest wolves or worse have been circling the area at night.", dangerLevel: "Moderate", encounterId: null, mapId: "settlement_outskirts", rewards: [ { type: "gold", amount: 60 }, { type: "influence", amount: 1 } ], citizenId: "citizen_003", dialogue: "I've seen tracks near the forge that concern me. Help me patrol the perimeter and we'll keep everyone safe.", summary: "Help Robert patrol the settlement boundaries", questAsset: "Bin/Contents/UI/Containers/Paper_1.png" }, "quest_james_scouting": { id: "quest_james_scouting", type: "citizen_request", title: "Training Exercise Gone Wrong", description: "Young James Miller went on a training patrol and stumbled upon what appears to be a bandit scout camp. He's requesting backup to clear them out before they report back to their main force. This is an opportunity to strike before they're prepared.", dangerLevel: "Moderate", encounterId: "tutorial_fight", mapId: "woodlands", rewards: [ { type: "gold", amount: 50 }, { type: "influence", amount: 1 } ], citizenId: "citizen_006", dialogue: "I know I'm young, but I can handle myself in a fight. I just need someone with experience to help me take down this scout before he alerts the others.", summary: "Help James eliminate a bandit scout before he can report back", questAsset: "Bin/Contents/UI/Containers/Paper_1.png" }, "task_gather_mushrooms": { id: "task_gather_mushrooms", type: "repeatable_task", title: "Gather Mushrooms", description: "The village needs fresh mushrooms from the forest for cooking and medicine. Collect 10 mushrooms from the woodland area.", dangerLevel: "Low", encounterId: null, mapId: "woodlands", eventId: "quest_gather_mushrooms", rewards: [ { type: "gold", amount: 15 } ], objectives: [ { text: "Collect 10 mushrooms from the forest", completed: false } ], iterable: true, autoComplete: true, dialogue: "The forest has plenty of mushrooms this season. Bring me 10 and I'll pay you fair coin.", summary: "Collect 10 mushrooms from the woodland", questAsset: "Bin/Contents/UI/Containers/Paper_1.png" }, "task_patrol_roads": { id: "task_patrol_roads", type: "repeatable_task", title: "Patrol the Roads", description: "Regular patrols keep the trade routes safe from bandits and wild animals. Walk the main road and report any threats.", dangerLevel: "Moderate", encounterId: null, mapId: "woodlands", rewards: [ { type: "gold", amount: 25 }, { type: "influence", amount: 1 } ], objectives: [ { text: "Patrol the northern trade route", completed: false }, { text: "Check the southern crossroads", completed: false } ], iterable: true, autoComplete: true, dialogue: "The roads need watching. Walk them and make sure travelers can pass safely.", summary: "Patrol the trade routes and ensure safety", questAsset: "Bin/Contents/UI/Containers/Paper_1.png" }, "task_collect_firewood": { id: "task_collect_firewood", type: "repeatable_task", title: "Collect Firewood", description: "The village stores need constant restocking with firewood for cooking and heating. Gather wood from fallen branches in the forest.", dangerLevel: "Low", encounterId: null, mapId: "woodlands", eventId: "quest_collect_firewood", rewards: [ { type: "gold", amount: 10 } ], objectives: [ { text: "Gather 15 pieces of firewood", completed: false } ], iterable: true, autoComplete: true, dialogue: "Winter is always coming, friend. We can never have too much firewood.", summary: "Gather 15 pieces of firewood for the village", questAsset: "Bin/Contents/UI/Containers/Paper_1.png" }, "task_hunt_game": { id: "task_hunt_game", type: "repeatable_task", title: "Hunt for Game", description: "Fresh meat is always in demand. Hunt rabbits or deer in the woodland and bring the meat back to the village.", dangerLevel: "Low", encounterId: null, mapId: "woodlands", eventId: "quest_hunt_game", rewards: [ { type: "gold", amount: 20 } ], objectives: [ { text: "Hunt 3 animals for meat", completed: false } ], iterable: true, autoComplete: true, dialogue: "Fresh meat fetches good coin at market. Bring me what you hunt and I'll pay you well.", summary: "Hunt 3 animals and bring back fresh meat", questAsset: "Bin/Contents/UI/Containers/Paper_1.png" }, "task_deliver_supplies": { id: "task_deliver_supplies", type: "repeatable_task", title: "Deliver Supplies", description: "Supplies need to be delivered between the village and nearby farms. Carry these goods safely to their destination.", dangerLevel: "Low", encounterId: null, mapId: null, rewards: [ { type: "gold", amount: 12 } ], objectives: [ { text: "Deliver supplies to the farm", completed: false } ], iterable: true, autoComplete: true, dialogue: "Can you take these supplies to the farm? It's not far, and I'll make it worth your while.", summary: "Deliver supplies to a nearby farm", questAsset: "Bin/Contents/UI/Containers/Paper_1.png" }, "task_collect_grass": { id: "task_collect_grass", type: "repeatable_task", title: "Harvest Wild Grass", description: "Gather wild grass and reeds from the woodland for bedding and thatching.", dangerLevel: "Low", encounterId: null, mapId: "woodlands", eventId: "quest_collect_grass", rewards: [ { type: "gold", amount: 12 } ], objectives: [ { text: "Harvest bundles of wild grass", completed: false } ], iterable: true, autoComplete: true, dialogue: "We could use more reed bundles for bedding and roofs. Bring some back from the woods.", summary: "Gather wild grass and reeds for the village", questAsset: "Bin/Contents/UI/Containers/Paper_1.png" }, "task_collect_rare_wood": { id: "task_collect_rare_wood", type: "repeatable_task", title: "Collect Rare Hardwood", description: "A carpenter wants a small stockpile of rare hardwood from deeper in the woodland for special repairs.", dangerLevel: "Moderate", encounterId: null, mapId: "woodlands", eventId: "quest_collect_rare_wood", rewards: [ { type: "gold", amount: 30 }, { type: "influence", amount: 1 } ], objectives: [ { text: "Collect rare hardwood from the woodland", completed: false } ], iterable: true, autoComplete: true, dialogue: "I need hardy timber for a difficult repair. Find me some rare hardwood from the deep woods.", summary: "Gather rare hardwood for the carpenter", questAsset: "Bin/Contents/UI/Containers/Paper_1.png" } }>> <<run setup.dialogueQuests = { "test_quest_01": { id: "test_quest_01", type: "dialogue_quest", title: "The Merchant's Plea", description: "A traveling merchant asks you to recover his stolen goods from bandits hiding in the forest.", dangerLevel: "Moderate", encounterId: null, mapId: null, rewards: [ { type: "gold", amount: 100 }, { type: "influence", amount: 2 } ], objectives: [ { text: "Find the bandit hideout", completed: false }, { text: "Recover the stolen goods", completed: false }, { text: "Return to the merchant", completed: false } ], giverName: "Traveling Merchant", dialogue: "Please, I beg of you! Those bandits took everything I had.", summary: "Recover stolen goods from forest bandits" }, "test_quest_02": { id: "test_quest_02", type: "dialogue_quest", title: "Guard Duty", description: "The guard captain needs an extra hand patrolling the northern wall tonight. Strange sounds have been reported.", dangerLevel: "Low", encounterId: null, mapId: null, rewards: [ { type: "gold", amount: 50 } ], objectives: [ { text: "Meet the captain at the northern gate", completed: false }, { text: "Patrol the wall", completed: false }, { text: "Report any findings", completed: false } ], giverName: "Guard Captain", dialogue: "I could use someone with your skills tonight. The men are spooked.", summary: "Help patrol the northern wall" }, "quest_find_family": { id: "quest_find_family", type: "dialogue_quest", title: "Find Mother and Sister", description: "Your lands are under attack. You have instructed Rodric to get Theron and hold the main castle door.", dangerLevel: "High", encounterId: null, mapId: "home_region", rewards: [], objectives: [ { text: "Find Mother and Sister", completed: false } ], giverName: "Roderic Kain", dialogue: "I should find my Mother and Sister as quickly as possible.", summary: "Look for Mother and Sister by going through locations in the castle.", metadata: { buildingActionLock: { allowActionIds: [ "visit_throne", "visit_throne_room", "visit_dining_hall", "visit_library", "visit_noble_quarters_a", "visit_noble_quarters_b", "visit_player_quarters", "enter_cellar" ], message: "Quest in progress. Only designated actions are available." } }, tutorial: { triggerOn: 'accepted', title: 'Castle Navigation', completeMessage: 'Search each floor carefully. Your Mother and Sister are waiting.', steps: [ { title: 'CASTLE FLOORS', desc: 'The castle has multiple floors. Use the floor buttons to navigate between the lower level, main floor, and upper chambers.', note: 'Each floor holds different rooms and secrets.', target: '.floor-navigation, .floor-buttons, [data-floor], .bi-floor-btn', position: 'right' }, { title: 'SEARCH LOCATIONS', desc: 'Click on room names or location buttons to enter and search each area. Your family may be hiding in any of these locations.', note: 'Check the throne room, bedchambers, and cellar thoroughly.', target: '.location-button, .room-button, .building-interior-location, .bi-location-btn', position: 'bottom' }, { title: 'TRACK YOUR QUEST', desc: 'Your quest objectives appear in the quest log. Complete each objective to progress through the story.', note: 'Time is of the essence. Find them before it is too late.', target: '.quest-tracker, .quest-objectives, [data-quest-panel]', position: 'left' } ] } }, "quest_aid_father": { id: "quest_aid_father", type: "dialogue_quest", title: "Aid Father", description: "Your lands are under attack. You have disobeyed your father's orders. You have instructed Rodric to get Theron and wait for you at the main castle door to aid your father in battle.", dangerLevel: "High", encounterId: null, mapId: "home_region", rewards: [], objectives: [ { text: "Reach the main castle door", completed: false } ], giverName: "Roderic Kain", dialogue: "There's no time to waste. I need to help my father on the battlefield.", summary: "Reach the main castle door by going through locations in the castle.", metadata: { buildingActionLock: { allowActionIds: [ "leave_castle", "visit_throne", "visit_throne_room", "visit_dining_hall", "visit_library", "visit_noble_quarters_a", "visit_noble_quarters_b", "visit_player_quarters", "enter_cellar" ], message: "Quest in progress. Only designated actions are available." } }, tutorial: { triggerOn: 'accepted', title: 'Racing to Battle', completeMessage: 'Make haste through the castle. Your father needs you at the main door.', steps: [ { title: 'NAVIGATE QUICKLY', desc: 'You must reach the main castle door. Navigate through the castle floors to find the fastest route.', note: 'Every moment counts. The sounds of battle grow louder.', target: '.floor-navigation, [data-floor]', position: 'right' }, { title: 'FIND THE DOOR', desc: 'The main castle door is on the main floor. Click through locations until you reach it.', note: 'Steel your nerves. Battle awaits beyond those doors.', target: '.location-button, .building-interior-location', position: 'bottom' } ] } }, "quest_aftermath": { id: "quest_aftermath", type: "dialogue_quest", title: "Aftermath", description: "My father Robert is dead. I should let my mother Elanor know of his passing.", dangerLevel: "Low", encounterId: null, mapId: "home_region", rewards: [], objectives: [ { text: "Bring the news to your mother, She's waiting in the throne room.", completed: false } ], giverName: "Theron Fenric", dialogue: "I can't believe my father's dead.", summary: "Click on the castle to enter it.", tutorial: { triggerOn: 'accepted', title: 'Bearer of Grief', allowActionIds: [ "visit_throne", "visit_throne_room", "visit_dining_hall", "visit_library", "visit_noble_quarters_a", "visit_noble_quarters_b", "visit_player_quarters" ], completeMessage: 'Your mother waits in the throne room. Be gentle with the news.', steps: [ { title: 'RETURN TO CASTLE', desc: 'Your mother Eleanor awaits news in the throne room. Enter the castle to find her.', note: 'The weight of leadership often begins with bearing terrible news.', target: '.castle-entrance, [data-building="castle"]', position: 'right' } ] } }, "quest_dear_sister": { id: "quest_dear_sister", type: "dialogue_quest", title: "Dear Sister", description: "To be continued.", dangerLevel: "Moderate", encounterId: null, mapId: "home_region", rewards: [], objectives: [ { text: "To be continued.", completed: false }, ], giverName: "Eleanor", dialogue: "To be continued", summary: "To be continued.", }, "quest_your_lands": { id: "quest_your_lands", type: "dialogue_quest", title: "Your Lands", description: "The Vargun raid has left your holdings in ruin. Homes are burned, farms are trampled, and what remains of your retinue is scattered and wounded. The people look to you now, their new Lord, to bring order back to the chaos.", dangerLevel: "Moderate", encounterId: null, mapId: "home_region", rewards: [ { type: "gold", amount: 500 }, { type: "influence", amount: 5 } ], objectives: [ { text: "Build a Woodcutter camp", completed: false }, { text: "Build a Farm", completed: false } ], giverName: "Elanor", dialogue: "The people look to you now, my son, to bring order back from the chaos.", summary: "Clear the ruins, rebuild the settlement, and restore the basic economy.", autoComplete: true, tutorial: { triggerOn: 'accepted', title: 'Rebuilding Your Domain', completeMessage: 'Your people await your leadership. Rebuild what was lost and restore prosperity to these lands.', requiredPassage: 'area1', steps: [ { title: 'DAMAGED BUILDING', desc: 'The raid has left buildings in ruin. Click on damaged or collapsed structures to view their status and available actions.', note: 'A lord must first survey the destruction before commanding repairs.', target: '.city-building-visual, .building-damaged, [data-building-id]', position: 'right' }, { title: 'BUILDING INFO', desc: 'When you click a building, an information panel appears showing its condition, level, and available actions.', note: 'Knowledge of your holdings is the first step to restoring them.', target: '.city-building-info-panel, .building-info, [data-building-info]', position: 'left' }, { title: 'REPAIR ACTION', desc: 'Click the Repair or Manage button to begin restoring a damaged building. This will require resources and time.', note: 'Gold and materials flow from your coffers to rebuild stone and timber.', target: '.city-building-action-primary, .repair-button, [data-action="repair"]', position: 'bottom' }, { title: 'CONSTRUCTION', desc: 'Buildings under construction show a progress bar. Time must pass for workers to complete their labor.', note: 'Patience. Rome was not built in a day, nor shall your domain be.', target: '.city-building-construction-bar, .construction-progress', position: 'top' }, { title: 'BUILD NEW', desc: 'To construct new buildings like farms or woodcutter camps, find empty plots and select what to build from the menu.', note: 'New structures bring new prosperity. Choose wisely where to invest.', target: '.building-menu, .build-button, [data-action="build"]', position: 'right' }, { title: 'SETTINGS', desc: 'Adjust audio, visuals, and interface options from here.', note: 'Tailor the experience to your liking.', target: '[data-gui-button="settings"]', position: 'right' }, { title: 'SAVE GAME', desc: 'Save your progress whenever you need to.', note: 'A wise lord saves often.', target: '[data-gui-button="save"]', position: 'right' }, { title: 'RETURN HOME', desc: 'Return to the land overview to manage your holdings.', note: 'Your domain spans beyond these walls.', target: '[data-gui-button="return-home"]', position: 'right' }, { title: 'SKIP TIME', desc: 'Advance time in large steps to progress buildings and events.', note: 'Use it sparingly.', target: '[data-gui-button="skip-time"]', position: 'right' }, { title: 'TIME & DAY', desc: 'Current time and day are tracked here.', note: 'Keep an eye on the cycle.', target: '.time_and_money', position: 'left' }, { title: 'QUEST BOARD', desc: 'Check available quests and take on new work.', note: 'Opportunity awaits.', target: '.map-area-quest-board', position: 'top' }, { title: 'COMBAT TEST', desc: 'This button launches a test combat encounter.', note: 'Use it to practice tactics.', target: '.map-area-test-combat', position: 'top' }, { title: 'CASTLE MANAGEMENT', desc: 'Open the settlement overview to manage buildings and production.', note: 'Your seat of power.', target: '[data-gui-button="castle-management"]', position: 'left' }, { title: 'COMPANIONS', desc: 'Review companions, stats, and assignments.', note: 'Loyal blades are earned.', target: '[data-gui-button="companions"]', position: 'left' }, { title: 'QUEST LOG', desc: 'Open the quest UI to track objectives and progress.', note: 'Know your current charge.', target: '[data-gui-button="quest-ui"]', position: 'left' }, { title: 'WORLD MAP', desc: 'Travel beyond the castle to explore and act.', note: 'The realm is vast.', target: '[data-gui-button="world-map"]', position: 'left' }, { title: 'PRISON', desc: 'Manage prisoners and the dungeon.', note: 'Mercy and discipline, both have their place.', target: '[data-gui-button="prison"]', position: 'left' }, { title: 'INVENTORY', desc: 'Inspect items, gear, and supplies.', note: 'Preparation wins battles.', target: '[data-gui-button="inventory"]', position: 'left' } ] } } }>> <</silently>>
:: RandomEventDefinitions <<set $randomEventDefinitions = [ { id: "immigrant_caravan", category: "population", severity: "minor", triggerChance: 0.6, regionType: "any", weight: 7, fallbackChance: 0.35, message: "A weary caravan petitions for shelter at Blackmoor's gates", description: "Several families arrive at the castle seeking refuge. They carry few belongings but offer willing hands if granted safety behind the walls.", mediaType: "image", mediaPath: "Bin/Contents/Events/immigrant_caravan.jpg", effects: {}, choices: [ { text: "Welcome the caravan inside", outcomes: { gold: -25, happiness: +4, loyalty: +3, custom: { type: "connect", eventId: "immigrant_caravan_welcomed", delay: 0 } } }, { text: "Ask them to wait in the outer camps", outcomes: { happiness: -1, loyalty: +2, custom: { type: "connect", eventId: "immigrant_caravan_deferred", delay: 0 } } }, { text: "Refuse them entry", outcomes: { happiness: -5, loyalty: -3, custom: { type: "connect", eventId: "immigrant_caravan_refused", delay: 0 } } } ], conditions: { minAbsenceDays: 2, regionTriggers: ["reputation_sanctuary"] } }, { id: "immigrant_caravan_welcomed", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The caravan settles in", description: "Food and blankets are shared, and the newcomers pledge to serve the castle loyally.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "immigration", accept: true, summary: "The caravan found shelter within the castle walls.", happinessShift: 2, template: { families: [{ tag: "family_medium", count: 1 }], singles: [{ tag: "laborer", count: 2 }] } } } } ] }, { id: "immigrant_caravan_deferred", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The caravan waits outside", description: "The caravan is supplied with tents and rations outside the gate while their fate is considered.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "immigration", accept: false, defer: true, summary: "The caravan was told to make camp beyond the walls for now.", template: { families: [{ tag: "family_medium", count: 1 }], singles: [{ tag: "laborer", count: 2 }] } } } } ] }, { id: "immigrant_caravan_refused", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The caravan departs", description: "The gates remain closed and the travelers depart in search of aid elsewhere.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "immigration", accept: false, summary: "The caravan was turned away from the castle." } } } ] }, { id: "immigrant_artisans", category: "population", severity: "minor", triggerChance: 0.6, regionType: "any", weight: 5, fallbackChance: 0.25, message: "Skilled artisans from distant guilds request residence", description: "A pair of craftsmen and their apprentices arrive with tools and samples of their work, seeking a workshop within the castle's protection.", effects: {}, choices: [ { text: "Grant them quarters and a workshop", outcomes: { gold: -40, happiness: +3, loyalty: +2, custom: { type: "connect", eventId: "immigrant_artisans_accepted", delay: 0 } } }, { text: "Offer them temporary contracts", outcomes: { gold: -20, happiness: +1, custom: { type: "connect", eventId: "immigrant_artisans_temporary", delay: 0 } } }, { text: "Decline their request", outcomes: { happiness: -2, loyalty: -1, custom: { type: "connect", eventId: "immigrant_artisans_declined", delay: 0 } } } ], conditions: { minAbsenceDays: 3, regionTriggers: ["reputation_safety", "trade_routes"] } }, { id: "immigrant_artisans_accepted", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The artisans settle into their workshop", description: "The artisans pledge to elevate the castle's craft and train locals in their trade.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "immigration", accept: true, summary: "The artisans settled in and unpacked their tools.", happinessShift: 1, template: { families: [{ tag: "family_small", count: 1 }], singles: [{ tag: "artisan", count: 2 }] } } } } ] }, { id: "immigrant_artisans_temporary", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The artisans set up temporary stalls", description: "The artisans agree to work from the outer camps until permanent space can be arranged.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "immigration", accept: false, defer: true, summary: "The artisans set up temporary stalls outside the walls.", template: { families: [{ tag: "family_small", count: 1 }], singles: [{ tag: "artisan", count: 2 }] } } } } ] }, { id: "immigrant_artisans_declined", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The artisans move on", description: "The artisans depart disappointed, taking their skills elsewhere.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "immigration", accept: false, summary: "The artisans continued their travels in search of another lord." } } } ] }, { id: "immigrant_refugees", category: "population", severity: "moderate", triggerChance: 0.4, regionType: "any", weight: 6, fallbackChance: 0.3, message: "Refugees fleeing unrest seek sanctuary in Blackmoor", description: "A large group of displaced villagers arrives with little more than the clothes on their backs, pleading for protection and a fresh start.", effects: {}, choices: [ { text: "Shelter as many as we can", outcomes: { food: -60, happiness: +5, loyalty: +4, custom: { type: "connect", eventId: "immigrant_refugees_sheltered", delay: 0 } } }, { text: "Provide supplies but limit entry", outcomes: { food: -40, happiness: -1, loyalty: +2, custom: { type: "connect", eventId: "immigrant_refugees_limited", delay: 0 } } }, { text: "Turn them away with regrets", outcomes: { happiness: -7, loyalty: -5, custom: { type: "connect", eventId: "immigrant_refugees_rejected", delay: 0 } } } ], conditions: { minAbsenceDays: 4, regionTriggers: ["border_region", "low_security"] } }, { id: "immigrant_refugees_sheltered", category: "population", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "The refugees settle in", description: "The weary refugees are ushered inside and allocated space in unused halls.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "immigration", accept: true, summary: "The refugees were accepted and began settling into spare quarters.", happinessShift: 3, template: { families: [{ tag: "family_large", count: 1 }, { tag: "family_medium", count: 1 }], singles: [{ tag: "healer", count: 1 }, { tag: "laborer", count: 2 }] } } } } ] }, { id: "immigrant_refugees_limited", category: "population", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "The refugees camp outside", description: "Tents and rations are issued outside the walls while you weigh longer-term options.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "immigration", accept: false, defer: true, summary: "The refugees camped beyond the walls awaiting your final decision.", template: { families: [{ tag: "family_large", count: 1 }, { tag: "family_medium", count: 1 }], singles: [{ tag: "healer", count: 1 }, { tag: "laborer", count: 2 }] } } } } ] }, { id: "immigrant_refugees_rejected", category: "population", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "The refugees depart in sorrow", description: "The refugees depart in sorrow, and murmurs of cold-hearted rule spread through nearby villages.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "immigration", accept: false, summary: "The refugees were denied sanctuary and moved on." } } } ] }, { id: "immigrant_pilgrims", category: "population", severity: "minor", triggerChance: 0.6, regionType: "any", weight: 4, fallbackChance: 0.2, message: "Elder pilgrims ask to retire within the castle's chapel district", description: "A small circle of elderly devotees arrives with scrolls and relics, wishing to spend their remaining days in quiet service.", effects: {}, choices: [ { text: "Offer them residence near the shrine", outcomes: { food: -20, happiness: +3, loyalty: +3, custom: { type: "connect", eventId: "immigrant_pilgrims_accepted", delay: 0 } } }, { text: "Let them rest in the outer cloister", outcomes: { food: -10, custom: { type: "connect", eventId: "immigrant_pilgrims_cloister", delay: 0 } } }, { text: "Decline with blessings", outcomes: { happiness: -1, loyalty: -2, custom: { type: "connect", eventId: "immigrant_pilgrims_declined", delay: 0 } } } ], conditions: { minAbsenceDays: 2, regionTriggers: ["pious_population"] } }, { id: "immigrant_pilgrims_accepted", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The pilgrims settle near the shrine", description: "The pilgrims settle into converted guest rooms and promise to counsel the faithful.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "immigration", accept: true, summary: "The pilgrims were welcomed into the shrine district.", happinessShift: 2, template: { singles: [{ tag: "elder", count: 3 }] } } } } ] }, { id: "immigrant_pilgrims_cloister", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The pilgrims rest in the cloister", description: "Temporary shelters are raised outside the walls while the chapel debates their request.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "immigration", accept: false, defer: true, summary: "The pilgrims agreed to wait in the cloister gardens.", template: { singles: [{ tag: "elder", count: 3 }] } } } } ] }, { id: "immigrant_pilgrims_declined", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The pilgrims depart", description: "The pilgrims accept your refusal and depart to another sanctuary.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "immigration", accept: false, summary: "The pilgrims continued their journey elsewhere." } } } ] }, // === EXTERNAL THREATS === { id: "bandit_raids", category: "threat", severity: "moderate", triggerChance: 0.4, regionType: "any", weight: 12, timeLimit: 5, message: "Bandits have been raiding throughout Blackmoor", description: "Organized bands of outlaws strike at trade routes and vulnerable settlements. Caravans are robbed, stores looted, and citizens live in fear of the next attack.", effects: { gold: -100, security: -10, crime: +8, happiness: -12 }, failurePenalty: { gold: -200, security: -20, crime: +15, happiness: -25, description: "The bandits grew bolder and organized, devastating trade routes and terrorizing settlements unchecked." }, choices: [ { text: "Hire mercenaries to hunt down the bandits", outcomes: { gold: -150, security: +15, crime: -10, happiness: +8, custom: { type: "connect", eventId: "bandit_raids_mercenaries", delay: 0 } } }, { text: "Organize citizen militias for defense", outcomes: { security: +8, loyalty: +10, happiness: +5, population: -2, custom: { type: "connect", eventId: "bandit_raids_militia", delay: 0 } } }, { text: "Negotiate with bandit leaders for safe passage", outcomes: { gold: -80, security: -5, crime: +5, loyalty: -8, custom: { type: "connect", eventId: "bandit_raids_negotiate", delay: 0 } } } ], conditions: { minAbsenceDays: 2, regionTriggers: ["low_security", "high_wealth"] } }, { id: "bandit_raids_mercenaries", category: "threat", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Mercenaries mobilize against the bandits", description: "Professional fighters eliminate the bandit threat, restoring peace to the roads.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { quest: { type: "minor", status: "posted", title: "Break the Bandit Siege", description: "Hire seasoned mercenaries to sweep the trade roads and dismantle the bandit camps threatening Blackmoor's lifelines.", dangerLevel: "High", giverName: "Captain Thorne", rewards: [ { type: "gold", amount: 200 }, { type: "influence", amount: 1 } ], metadata: { source: "randomEvent", encounterId: "bandit_duo_fight", mapId: "woodlands", summary: "Finance a mercenary company to secure Blackmoor's trade routes.", eventCategory: "threat", eventMessage: "Bandits have been raiding throughout Blackmoor", citizenName: "Captain Thorne", title: "Marshal of the Watch" } }, onAccept: function(quest) { if (window.QuestTrackingSystem && quest) { window.QuestTrackingSystem.acceptQuest(quest.id); } } } } } ] }, { id: "bandit_raids_militia", category: "threat", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Citizen militias organize", description: "Citizens band together to protect their homes, though some are injured in the fights.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "bandit_raids_negotiate", category: "threat", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "An uneasy truce with the bandits", description: "An uneasy truce is struck, but citizens lose faith in your leadership.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "bandit_raids_with_guard", category: "threat", severity: "moderate", triggerChance: 0.5, regionType: "any", weight: 14, message: "Bandits raid Blackmoor, but your Guard responds swiftly", description: "Organized bands of outlaws attempt to strike at trade routes, but your appointed Guard has already mobilized defenses against the threat.", effects: { gold: -50, security: -5, crime: +4, happiness: -6 }, choices: [ { text: "Let the Guard handle it", outcomes: { gold: -25, security: +10, crime: -8, happiness: +8, custom: { type: "connect", eventId: "bandit_raids_guard_handles", delay: 0 } } }, { text: "Supplement with hired mercenaries", outcomes: { gold: -100, security: +18, crime: -15, happiness: +12, custom: { type: "connect", eventId: "bandit_raids_guard_mercenaries", delay: 0 } } }, { text: "Rely solely on citizen militias", outcomes: { security: +5, loyalty: +8, happiness: +3, population: -1, custom: { type: "connect", eventId: "bandit_raids_guard_militias", delay: 0 } } } ], conditions: { minAbsenceDays: 2, regionTriggers: ["has_guard", "low_security"] } }, { id: "bandit_raids_guard_handles", category: "threat", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "The Guard secures the roads", description: "The Guard's tactical expertise and preparation minimizes damage and boosts citizen morale.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "bandit_raids_guard_mercenaries", category: "threat", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Combined forces crush the bandits", description: "The combination of your Guard's leadership and mercenary force completely crushes the bandit threat.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "bandit_raids_guard_militias", category: "threat", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Citizens rally under the Guard", description: "Citizens rally under the Guard's direction, though casualties occur in the fighting.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "wolf_pack_attacks", category: "threat", severity: "minor", triggerChance: 0.6, regionType: "any", weight: 10, message: "Wolf packs threaten Blackmoor's settlements", description: "Hungry wolves venture from the deep forests, threatening livestock and lone travelers. Night watches double their vigilance as howls echo through the darkness.", effects: { food: -25, security: -5, happiness: -8 }, choices: [ { text: "Organize hunting parties to drive off the wolves", outcomes: { food: -10, security: +10, happiness: +5, loyalty: +5, custom: { type: "connect", eventId: "wolf_pack_hunted", delay: 0 } } }, { text: "Build stronger fences and increase patrols", outcomes: { food: -15, security: +8, happiness: -2, gold: -50, wood: -20, custom: { type: "connect", eventId: "wolf_pack_fortified", delay: 0 } } }, { text: "Retreat to fortified areas until wolves move on", outcomes: { food: -35, security: -10, happiness: -15, loyalty: -8, custom: { type: "connect", eventId: "wolf_pack_retreat", delay: 0 } } } ], conditions: { minAbsenceDays: 1, regionTriggers: ["winter_season", "food_shortage"] } }, { id: "wolf_pack_hunted", category: "threat", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The wolf threat is eliminated", description: "Brave hunters eliminate the threat and provide fresh meat for the settlements.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "wolf_pack_fortified", category: "threat", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Defenses are strengthened", description: "Defensive measures reduce attacks but require resources and constant vigilance.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "wolf_pack_retreat", category: "threat", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Settlements hunker down", description: "Safety is maintained but productivity suffers and people lose confidence.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "orc_warband", category: "threat", severity: "major", triggerChance: 0.2, regionType: "any", weight: 4, message: "An orc warband threatens Blackmoor", description: "Savage orcs from the badlands mass at the borders, raiding settlements and burning what they cannot steal. Their war drums echo ominously across the region.", effects: { population: -8, gold: -80, security: -20, happiness: -18, food: -40 }, choices: [ { text: "Rally all forces for a decisive battle", outcomes: { population: -5, gold: -50, security: +15, happiness: +10, loyalty: +20, custom: { type: "connect", eventId: "orc_warband_battle", delay: 0 } } }, { text: "Pay tribute to make them leave peacefully", outcomes: { gold: -200, food: -100, security: -10, loyalty: -15, happiness: -10, custom: { type: "connect", eventId: "orc_warband_tribute", delay: 0 } } }, { text: "Fortify settlements and wait for them to leave", outcomes: { population: -12, gold: -120, food: -80, happiness: -25, wood: -50, stone: -30, custom: { type: "connect", eventId: "orc_warband_siege", delay: 0 } } } ], conditions: { minAbsenceDays: 6, regionTriggers: ["border_region", "valuable_resources"] } }, { id: "orc_warband_battle", category: "threat", severity: "major", triggerChance: 0.0, regionType: "any", weight: 1, message: "Victory over the orcs", description: "Victory comes at a cost but the orc threat is eliminated and morale soars.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "orc_warband_tribute", category: "threat", severity: "major", triggerChance: 0.0, regionType: "any", weight: 1, message: "The orcs depart with tribute", description: "The orcs depart with their tribute but your weakness invites future attacks.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "orc_warband_siege", category: "threat", severity: "major", triggerChance: 0.0, regionType: "any", weight: 1, message: "The siege finally ends", description: "The siege drags on, causing enormous suffering before the orcs finally withdraw.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "merchant_caravan_destroyed", category: "threat", severity: "minor", triggerChance: 0.6, regionType: "any", weight: 8, message: "Merchant caravans attacked on the roads", description: "Trade convoys fall victim to raiders and monsters. Goods are scattered across the roads, and surviving merchants spread tales of danger, discouraging future trade.", effects: { gold: -60, food: -20, stone: -15, happiness: -5 }, choices: [ { text: "Send guards to escort future caravans", outcomes: { gold: -40, security: +10, happiness: +8, loyalty: +5, custom: { type: "connect", eventId: "merchant_caravan_escorted", delay: 0 } } }, { text: "Compensate merchants and salvage what goods remain", outcomes: { gold: -80, food: +10, stone: +8, happiness: +5, loyalty: +8, custom: { type: "connect", eventId: "merchant_caravan_compensated", delay: 0 } } }, { text: "Warn merchants they travel at their own risk", outcomes: { gold: -80, food: -30, stone: -25, happiness: -15, loyalty: -10, custom: { type: "connect", eventId: "merchant_caravan_warned", delay: 0 } } } ], conditions: { minAbsenceDays: 2, regionTriggers: ["active_trade_routes"] } }, { id: "merchant_caravan_escorted", category: "threat", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Trade routes secured", description: "Protected trade routes restore merchant confidence and commerce flourishes.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "merchant_caravan_compensated", category: "threat", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Merchants are compensated", description: "Your generosity maintains goodwill and some goods are recovered.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "merchant_caravan_warned", category: "threat", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Trade declines", description: "Trade diminishes as merchants avoid dangerous routes and blame poor leadership.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, // === SOCIAL UNREST === { id: "mass_migration", category: "social", severity: "moderate", triggerChance: 0.4, regionType: "any", weight: 7, message: "Refugees flood into Blackmoor", description: "Displaced peoples from distant conflicts seek shelter in Blackmoor. While they bring strong backs, they also strain resources and housing, creating tension with existing residents.", effects: { population: +12, happiness: -15, crime: +10, food: -30 }, choices: [ { text: "Welcome refugees and establish integration programs", outcomes: { population: +15, happiness: -5, loyalty: +10, gold: -120, custom: { type: "connect", eventId: "mass_migration_integrate", delay: 0 } } }, { text: "Accept refugees but provide minimal support", outcomes: { population: +12, happiness: -15, crime: +10, loyalty: -5, custom: { type: "connect", eventId: "mass_migration_minimal", delay: 0 } } }, { text: "Turn away most refugees to protect resources", outcomes: { population: +3, happiness: +5, loyalty: -10, crime: +5, custom: { type: "connect", eventId: "mass_migration_refused", delay: 0 } } } ], conditions: { minAbsenceDays: 3, regionTriggers: ["regional_conflicts", "reputation_sanctuary"] } }, { id: "mass_migration_integrate", category: "social", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Refugees integrate successfully", description: "Proper integration reduces tensions and builds a stronger community.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "mass_migration_minimal", category: "social", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Refugees struggle with hardship", description: "Refugees settle in but face hardship, creating ongoing social problems.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "mass_migration_refused", category: "social", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Most refugees turned away", description: "Resources are preserved but your reputation for compassion suffers.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "worker_strike", category: "social", severity: "minor", triggerChance: 0.6, regionType: "any", weight: 9, message: "Workers strike across Blackmoor", description: "Laborers in mines, farms, and workshops down their tools, demanding better conditions and fair pay. Production grinds to a halt until their grievances are addressed.", effects: { wood: -40, stone: -40, food: -30, happiness: -10, crime: +5 }, choices: [ { text: "Negotiate fair wages and improved conditions", outcomes: { wood: -10, stone: -10, food: -5, happiness: +15, loyalty: +12, gold: -100, custom: { type: "connect", eventId: "worker_strike_negotiate", delay: 0 } } }, { text: "Force workers back with threats and penalties", outcomes: { wood: -20, stone: -20, happiness: -20, loyalty: -15, crime: +10, security: -5, custom: { type: "connect", eventId: "worker_strike_force", delay: 0 } } }, { text: "Replace striking workers with new laborers", outcomes: { wood: -60, stone: -60, food: -40, happiness: -15, loyalty: -20, population: -5, custom: { type: "connect", eventId: "worker_strike_replace", delay: 0 } } } ], conditions: { minAbsenceDays: 2, regionTriggers: ["low_happiness", "high_taxation"] } }, { id: "worker_strike_negotiate", category: "social", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Workers return satisfied", description: "Workers return satisfied and productivity increases with improved morale.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "worker_strike_force", category: "social", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Workers forced back to work", description: "Production resumes but resentment runs deep and future unrest is likely.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "worker_strike_replace", category: "social", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Striking workers replaced", description: "New workers are less skilled and the community is divided by your harsh response.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "religious_tensions", category: "social", severity: "moderate", triggerChance: 0.4, regionType: "any", weight: 5, message: "Religious conflicts divide Blackmoor", description: "Competing faiths clash over doctrine and influence, splitting communities along religious lines. Heated debates turn to violence, threatening the peace.", effects: { happiness: -20, crime: +12, loyalty: -10 }, choices: [ { text: "Organize interfaith dialogue and compromise", outcomes: { happiness: -5, crime: -5, loyalty: +10, gold: -80, custom: { type: "connect", eventId: "religious_tensions_dialogue", delay: 0 } } }, { text: "Enforce strict separation and religious laws", outcomes: { happiness: -10, crime: -8, loyalty: -5, security: +10, custom: { type: "connect", eventId: "religious_tensions_enforce", delay: 0 } } }, { text: "Let religious communities settle matters themselves", outcomes: { happiness: -30, crime: +20, loyalty: -15, security: -10, custom: { type: "connect", eventId: "religious_tensions_escalate", delay: 0 } } } ], conditions: { minAbsenceDays: 4, regionTriggers: ["diverse_population", "religious_buildings"] } }, { id: "religious_tensions_dialogue", category: "social", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Faiths find common ground", description: "Patient mediation helps different faiths find common ground and coexist.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "religious_tensions_enforce", category: "social", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Religious laws enforced", description: "Order is maintained through rules but underlying tensions remain.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "religious_tensions_escalate", category: "social", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Violence escalates", description: "Violence escalates as extremists on both sides clash without restraint.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "noble_corruption", category: "social", severity: "moderate", triggerChance: 0.4, regionType: "any", weight: 6, message: "Corruption spreads among Blackmoor's nobles", description: "Local nobles embezzle funds and abuse their positions for personal gain. Word spreads of their misdeeds, eroding trust in leadership and breeding cynicism.", effects: { gold: -120, loyalty: -15, crime: +8, happiness: -12 }, choices: [ { text: "Investigate thoroughly and punish the guilty", outcomes: { gold: -80, loyalty: +20, happiness: +15, crime: -10, security: +5, custom: { type: "connect", eventId: "noble_corruption_punish", delay: 0 } } }, { text: "Quietly replace corrupt nobles with loyal ones", outcomes: { gold: -100, loyalty: +5, happiness: -5, crime: -3, custom: { type: "connect", eventId: "noble_corruption_replace", delay: 0 } } }, { text: "Accept the corruption as the cost of doing business", outcomes: { gold: -200, loyalty: -25, happiness: -20, crime: +15, custom: { type: "connect", eventId: "noble_corruption_accept", delay: 0 } } } ], conditions: { minAbsenceDays: 4, regionTriggers: ["high_wealth", "weak_governance"] } }, { id: "noble_corruption_punish", category: "social", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Justice is served", description: "Justice is served and public faith in leadership is restored.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "noble_corruption_replace", category: "social", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Corrupt nobles quietly replaced", description: "The problem is contained but rumors persist about cover-ups.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "noble_corruption_accept", category: "social", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Corruption becomes normalized", description: "Corruption becomes normalized and spreads throughout the administration.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, // === ECONOMIC DISRUPTION === { id: "crop_failure", category: "economic", severity: "moderate", triggerChance: 0.4, regionType: "any", weight: 10, timeLimit: 3, message: "Crops fail across Blackmoor's farms", description: "Blight, pests, or poor weather destroy harvests throughout the region. Granaries empty quickly, and food becomes a precious commodity as hunger stalks the land.", effects: { food: -150, happiness: -18, crime: +12 }, failurePenalty: { food: -200, happiness: -30, crime: +20, loyalty: -15, description: "Widespread famine grips the region as starvation and desperation turn neighbors against each other." }, choices: [ { text: "Import emergency food supplies and distribute freely", outcomes: { food: +100, happiness: +10, loyalty: +15, gold: -300, custom: { type: "connect", eventId: "crop_failure_import", delay: 0 } } }, { text: "Implement strict rationing to stretch reserves", outcomes: { food: -100, happiness: -10, crime: +5, loyalty: +5, security: +5, custom: { type: "connect", eventId: "crop_failure_ration", delay: 0 } } }, { text: "Let market forces determine food distribution", outcomes: { food: -180, happiness: -30, crime: +20, loyalty: -15, gold: +50, custom: { type: "connect", eventId: "crop_failure_market", delay: 0 } } } ], conditions: { minAbsenceDays: 3, regionTriggers: ["agricultural_region", "bad_weather"] } }, { id: "crop_failure_import", category: "economic", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Food crisis averted", description: "Generous aid prevents famine and earns deep gratitude from the people.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "crop_failure_ration", category: "economic", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Rationing prevents worst starvation", description: "Careful management prevents the worst starvation but people remain hungry.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "crop_failure_market", category: "economic", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Market forces cause unrest", description: "The wealthy eat while the poor starve, causing widespread unrest.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ] }, { id: "mine_collapse", category: "economic", severity: "moderate", triggerChance: 0.4, regionType: "any", weight: 7, message: "Mine collapse disrupts stone production", description: "A major mine collapses, trapping workers and halting stone extraction. Rescue efforts are costly, and the loss of production impacts construction projects region-wide.", effects: { stone: -100, gold: -80, population: -3, happiness: -15 }, choices: [ { text: "Launch immediate rescue operations and safety improvements", outcomes: { stone: -50, gold: -150, population: +1, happiness: +10, loyalty: +15, custom: { type: "connect", eventId: "mine_collapse_rescue", delay: 0 } } }, { text: "Clear debris and reopen with basic safety measures", outcomes: { stone: -80, gold: -100, happiness: -5, loyalty: +5, custom: { type: "connect", eventId: "mine_collapse_reopen", delay: 0 } } }, { text: "Abandon the mine and write off the losses", outcomes: { stone: -150, gold: -50, happiness: -25, loyalty: -10, population: -2, custom: { type: "connect", eventId: "mine_collapse_abandon", delay: 0 } } } ], conditions: { minAbsenceDays: 3, regionTriggers: ["mining_operations", "geological_instability"] } }, { id: "mine_collapse_rescue", category: "economic", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Lives saved, safety improved", description: "Lives are saved and improved safety restores worker confidence.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "mine_collapse_reopen", category: "economic", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Mine reopens with basic safety", description: "Production resumes quickly but workers remain nervous about safety.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "mine_collapse_abandon", category: "economic", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Mine abandoned", description: "Miners lose their livelihoods and the community loses a vital resource.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "trade_embargo", category: "economic", severity: "minor", triggerChance: 0.6, regionType: "any", weight: 8, message: "Trade embargo affects Blackmoor", description: "Neighboring regions impose trade restrictions, cutting off vital supply lines. Exotic goods disappear from markets, and merchants seek new routes at great expense.", effects: { gold: -90, food: -25, happiness: -10 }, choices: [ { text: "Negotiate diplomatic solutions to restore trade", outcomes: { gold: -50, food: +10, happiness: +8, loyalty: +10, custom: { type: "connect", eventId: "trade_embargo_negotiate", delay: 0 } } }, { text: "Find alternative trading partners and routes", outcomes: { gold: -60, food: -10, happiness: -5, loyalty: +5, custom: { type: "connect", eventId: "trade_embargo_alternative", delay: 0 } } }, { text: "Become self-sufficient and ignore foreign trade", outcomes: { gold: -120, food: -50, happiness: -20, loyalty: -10, wood: -30, custom: { type: "connect", eventId: "trade_embargo_selfsufficient", delay: 0 } } } ], conditions: { minAbsenceDays: 5, regionTriggers: ["diplomatic_tensions", "trade_dependent"] } }, { id: "trade_embargo_negotiate", category: "economic", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Trade routes restored", description: "Skillful diplomacy reopens trade routes and restores prosperity.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "trade_embargo_alternative", category: "economic", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "New trade partnerships formed", description: "New partnerships are established but at higher costs and effort.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "trade_embargo_selfsufficient", category: "economic", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Isolation causes hardship", description: "Isolation causes hardship as the economy struggles without trade.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "tool_shortage", category: "economic", severity: "minor", triggerChance: 0.6, regionType: "any", weight: 9, message: "Tool shortage hampers production", description: "A shortage of quality tools and implements reduces efficiency across all industries. Blacksmiths cannot keep pace with demand, and worn tools break frequently.", effects: { wood: -30, stone: -30, food: -20, happiness: -8 }, choices: [ { text: "Import quality tools and expand smithy operations", outcomes: { wood: +20, stone: +20, food: +10, happiness: +10, gold: -150, custom: { type: "connect", eventId: "tool_shortage_import", delay: 0 } } }, { text: "Repair and maintain existing tools more carefully", outcomes: { wood: -15, stone: -15, food: -10, happiness: -2, gold: -50, custom: { type: "connect", eventId: "tool_shortage_maintain", delay: 0 } } }, { text: "Make do with whatever tools are available", outcomes: { wood: -50, stone: -50, food: -30, happiness: -15, loyalty: -8, custom: { type: "connect", eventId: "tool_shortage_makedo", delay: 0 } } } ], conditions: { minAbsenceDays: 2, regionTriggers: ["high_production", "resource_scarcity"] } }, { id: "tool_shortage_import", category: "economic", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Productivity boosted", description: "Investment in tools and smithies boosts productivity across all sectors.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "tool_shortage_maintain", category: "economic", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Production below optimal", description: "Careful maintenance helps but production remains below optimal levels.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "tool_shortage_makedo", category: "economic", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Poor tools cause problems", description: "Poor tools lead to accidents, delays, and frustrated workers.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, // === HEALTH CRISES === { id: "food_poisoning_outbreak", category: "health", severity: "minor", triggerChance: 0.6, regionType: "any", weight: 12, timeLimit: 2, message: "Food poisoning outbreak in Blackmoor", description: "Contaminated grain or spoiled meat sickens dozens across multiple settlements. Trust in food supplies wavers, and careful inspection becomes necessary for all provisions.", effects: { population: -5, happiness: -12, food: -40 }, failurePenalty: { population: -12, happiness: -25, food: -70, loyalty: -20, description: "The outbreak spreads unchecked, claiming lives and destroying trust in leadership as the contamination spreads." }, choices: [ { text: "Destroy contaminated supplies and provide medical care", outcomes: { population: -2, happiness: +5, food: -60, gold: -80, loyalty: +10, custom: { type: "connect", eventId: "food_poisoning_destroy", delay: 0 } } }, { text: "Implement strict food inspection and storage protocols", outcomes: { population: -3, happiness: -5, food: -50, gold: -60, security: +5, custom: { type: "connect", eventId: "food_poisoning_inspect", delay: 0 } } }, { text: "Wait for the outbreak to run its course naturally", outcomes: { population: -8, happiness: -20, food: -30, loyalty: -15, custom: { type: "connect", eventId: "food_poisoning_wait", delay: 0 } } } ], conditions: { minAbsenceDays: 1, regionTriggers: ["poor_food_storage", "hot_weather"] } }, { id: "food_poisoning_destroy", category: "health", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Outbreak contained", description: "Quick action prevents further spread and restores public confidence.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "food_poisoning_inspect", category: "health", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "New protocols in place", description: "New procedures prevent future outbreaks but require ongoing resources.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "food_poisoning_wait", category: "health", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Trust erodes", description: "More people fall ill and trust in leadership erodes significantly.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "dysentery_spread", category: "health", severity: "moderate", triggerChance: 0.4, regionType: "any", weight: 8, message: "Dysentery spreads through Blackmoor", description: "Poor sanitation and contaminated water sources lead to widespread illness. The disease spreads quickly in crowded areas, forcing quarantine measures.", effects: { population: -10, happiness: -20, crime: +8 }, choices: [ { text: "Build proper sanitation and clean water systems", outcomes: { population: -3, happiness: +5, crime: -5, gold: -200, stone: -50, loyalty: +15, custom: { type: "connect", eventId: "dysentery_sanitation", delay: 0 } } }, { text: "Enforce quarantine and provide basic medical care", outcomes: { population: -8, happiness: -10, crime: +3, gold: -100, security: +8, custom: { type: "connect", eventId: "dysentery_quarantine", delay: 0 } } }, { text: "Isolate affected areas and wait for the disease to pass", outcomes: { population: -15, happiness: -35, crime: +15, loyalty: -20, security: -10, custom: { type: "connect", eventId: "dysentery_isolate", delay: 0 } } } ], conditions: { minAbsenceDays: 3, regionTriggers: ["poor_sanitation", "overcrowding"] } }, { id: "dysentery_sanitation", category: "health", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Disease stopped", description: "Infrastructure investment stops the disease and prevents future outbreaks.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "dysentery_quarantine", category: "health", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Spread limited", description: "Containment limits the spread but many still suffer from the disease.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "dysentery_isolate", category: "health", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Massive casualties", description: "Neglect leads to massive casualties and social breakdown in affected areas.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "mysterious_illness", category: "health", severity: "major", triggerChance: 0.2, regionType: "any", weight: 3, message: "A mysterious illness plagues Blackmoor", description: "An unknown disease with strange symptoms baffles healers and terrifies the populace. Some whisper of curses or dark magic as the affliction spreads without pattern.", effects: { population: -12, happiness: -25, loyalty: -8, crime: +15 }, choices: [ { text: "Seek magical or scholarly expertise to understand the illness", outcomes: { population: -5, happiness: -10, loyalty: +10, gold: -150, custom: { type: "connect", eventId: "mysterious_illness_expertise", delay: 0 } } }, { text: "Quarantine all affected and hope it burns itself out", outcomes: { population: -10, happiness: -20, loyalty: -5, crime: +8, security: +5, custom: { type: "connect", eventId: "mysterious_illness_quarantine", delay: 0 } } }, { text: "Dismiss talk of curses and treat it like any other disease", outcomes: { population: -18, happiness: -35, loyalty: -15, crime: +25, security: -15, custom: { type: "connect", eventId: "mysterious_illness_dismiss", delay: 0 } } } ], conditions: { minAbsenceDays: 7, regionTriggers: ["magical_activity", "foreign_contact"] } }, { id: "mysterious_illness_expertise", category: "health", severity: "major", triggerChance: 0.0, regionType: "any", weight: 1, message: "Disease contained", description: "Expert knowledge helps contain the mysterious disease and calm public fears.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "mysterious_illness_quarantine", category: "health", severity: "major", triggerChance: 0.0, regionType: "any", weight: 1, message: "Fear and suspicion", description: "Isolation slows the spread but fear and suspicion grip the quarantined areas.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "mysterious_illness_dismiss", category: "health", severity: "major", triggerChance: 0.0, regionType: "any", weight: 1, message: "Panic spreads", description: "Denial leads to panic as the illness spreads and people lose faith in authority.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, // === POSITIVE EVENTS === { id: "merchant_festival", category: "positive", severity: "minor", triggerChance: 0.6, regionType: "any", weight: 15, message: "Merchant festival brings prosperity to Blackmoor", description: "Traveling merchants converge for a grand festival, bringing exotic goods and gold. The celebration lifts spirits and fills coffers throughout the region.", effects: { gold: +150, happiness: +15, loyalty: +8 }, choices: [ { text: "Sponsor grand festivities to maximize celebration", outcomes: { gold: +100, happiness: +25, loyalty: +15, custom: { type: "connect", eventId: "merchant_festival_sponsor", delay: 0 } } }, { text: "Tax the merchants heavily for the privilege", outcomes: { gold: +250, happiness: +5, loyalty: -5, custom: { type: "connect", eventId: "merchant_festival_tax", delay: 0 } } }, { text: "Let the festival proceed naturally", outcomes: { gold: +150, happiness: +15, loyalty: +8, custom: { type: "connect", eventId: "merchant_festival_natural", delay: 0 } } } ], conditions: { minAbsenceDays: 4, regionTriggers: ["stable_region", "trade_routes"] } }, { id: "merchant_festival_sponsor", category: "positive", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Festival becomes legendary", description: "Your generous support makes the festival legendary, earning great favor.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "merchant_festival_tax", category: "positive", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Merchants grumble", description: "Profits are high but merchants grumble about the excessive fees.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "merchant_festival_natural", category: "positive", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Festival succeeds", description: "The festival is successful and everyone benefits modestly.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "bountiful_harvest", category: "positive", severity: "minor", triggerChance: 0.6, regionType: "any", weight: 12, message: "Bountiful harvests bless Blackmoor", description: "Perfect weather and fertile soil produce exceptional crops across the region. Granaries overflow, and farmers sing as they gather the abundance.", effects: { food: +200, happiness: +18, loyalty: +10 }, choices: [ { text: "Celebrate with a grand harvest festival", outcomes: { food: +150, happiness: +25, loyalty: +20, gold: -80, custom: { type: "connect", eventId: "bountiful_harvest_festival", delay: 0 } } }, { text: "Store extra food for future lean times", outcomes: { food: +300, happiness: +12, loyalty: +8, security: +5, custom: { type: "connect", eventId: "bountiful_harvest_store", delay: 0 } } }, { text: "Export surplus crops for profit", outcomes: { food: +100, happiness: +10, loyalty: +5, gold: +200, custom: { type: "connect", eventId: "bountiful_harvest_export", delay: 0 } } } ], conditions: { minAbsenceDays: 3, regionTriggers: ["good_weather", "agricultural_focus"] } }, { id: "bountiful_harvest_festival", category: "positive", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Community bonds strengthen", description: "Joyous celebrations strengthen community bonds and morale soars.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "bountiful_harvest_store", category: "positive", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Food security ensured", description: "Practical planning ensures food security and demonstrates wise leadership.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "bountiful_harvest_export", category: "positive", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Trade brings wealth", description: "Trade brings wealth to the region while keeping enough food for local needs.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "mineral_discovery", category: "positive", severity: "moderate", triggerChance: 0.4, regionType: "any", weight: 6, message: "Rich mineral deposits discovered in Blackmoor", description: "Prospectors uncover valuable ore veins and gem deposits, bringing wealth and opportunity to the region. New mining claims spark a boom in activity.", effects: { stone: +120, gold: +200, happiness: +12, population: +5 }, choices: [ { text: "Establish regulated mining with safety standards", outcomes: { stone: +150, gold: +150, happiness: +20, loyalty: +15, population: +3, custom: { type: "connect", eventId: "mineral_discovery_regulated", delay: 0 } } }, { text: "Allow rapid extraction to maximize immediate profits", outcomes: { stone: +200, gold: +400, happiness: +15, population: +8, security: -5, custom: { type: "connect", eventId: "mineral_discovery_rapid", delay: 0 } } }, { text: "Limit mining to preserve the natural landscape", outcomes: { stone: +80, gold: +100, happiness: +8, loyalty: +10, population: +2, custom: { type: "connect", eventId: "mineral_discovery_preserve", delay: 0 } } } ], conditions: { minAbsenceDays: 6, regionTriggers: ["mountainous_terrain", "exploration_activity"] } }, { id: "mineral_discovery_regulated", category: "positive", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Lasting prosperity created", description: "Careful development creates lasting prosperity with minimal environmental damage.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "mineral_discovery_rapid", category: "positive", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Quick wealth gained", description: "Quick wealth flows but hasty mining creates future problems.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "mineral_discovery_preserve", category: "positive", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "Opportunities limited", description: "Conservation-minded approach pleases some but others feel opportunities are wasted.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "skilled_refugees", category: "positive", severity: "minor", triggerChance: 0.6, regionType: "any", weight: 10, message: "Skilled craftsmen seek refuge in Blackmoor", description: "Master artisans and learned scholars arrive seeking safety and new opportunities. Their skills and knowledge prove invaluable to local development.", effects: { population: +8, happiness: +10, wood: +40, stone: +40 }, choices: [ { text: "Welcome them and establish training programs", outcomes: { population: +12, happiness: +18, wood: +60, stone: +60, loyalty: +15, gold: -100, custom: { type: "connect", eventId: "skilled_refugees_welcome", delay: 0 } } }, { text: "Accept their skills but limit their numbers", outcomes: { population: +8, happiness: +10, wood: +40, stone: +40, loyalty: +5, custom: { type: "connect", eventId: "skilled_refugees_accept", delay: 0 } } }, { text: "Tax their services heavily to fund public works", outcomes: { population: +5, happiness: +5, wood: +20, stone: +20, gold: +150, loyalty: -5, custom: { type: "connect", eventId: "skilled_refugees_tax", delay: 0 } } } ], conditions: { minAbsenceDays: 4, regionTriggers: ["reputation_safety", "growing_economy"] } }, { id: "skilled_refugees_welcome", category: "positive", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Thriving skilled community", description: "Investment in integration creates a thriving community of skilled workers.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "skilled_refugees_accept", category: "positive", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Community balance maintained", description: "Moderate approach gains benefits while maintaining community balance.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "skilled_refugees_tax", category: "positive", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Revenue generated", description: "High taxes generate revenue but discourage skilled immigration.", effects: {}, choices: [{ text: "Continue", outcomes: {} }] }, { id: "food_starvation_initial", category: "health", severity: "major", triggerChance: 1.0, regionType: "any", weight: 15, message: "Starving people at the gates", citizenMetadata: { type: 'complainers', selectionMethod: 'unhappiest', count: 5, filters: { minHappiness: 40, maxHealth: null, requiredTraits: [], requiredProfessions: [], excludedProfessions: [] } }, description: "Food supplies have run completely dry. Desperate, hungry people gather at your gates, begging for sustenance. Their faces are gaunt, their children crying. They plead for any scraps you might spare.", effects: {}, choices: [ { text: "Attempt to persuade them to wait (Persuasion check)", diceRoll: { required: true, context: "persuasion", target: 5 }, outcomes: { happiness: +5, loyalty: +10, custom: { type: "connect", eventId: "food_starvation_grace", delay: 0, metadata: { gracePeriodDays: 4, conditionToMeet: "food > 0" } } }, failureOutcomes: { happiness: -20, loyalty: -15, security: -10, custom: { type: "connect", eventId: "food_starvation_persuade_failed", delay: 0 } } }, { text: "Disperse the crowd (Requires: Guard)", condition: "has_guard", outcomes: { happiness: -20, loyalty: -15, security: +10, crime: -5, custom: { type: "connect", eventId: "food_starvation_dispersed", delay: 0 } } }, { text: "Deflect the issue - 'Not handling this at the moment'", outcomes: { happiness: -15, loyalty: -10, security: -5, custom: { type: "connect", eventId: "food_starvation_deflected", delay: 0 } } } ], conditions: { minAbsenceDays: 1, regionTriggers: ["food_depleted"] } }, { id: "food_starvation_grace", category: "health", severity: "major", triggerChance: 0.0, regionType: "any", weight: 1, timeLimit: 4, message: "Waiting for promised food", description: "The people you persuaded are waiting patiently, but their stomachs are empty and time is running out. You promised them food within four days. If supplies don't arrive soon, their patience will turn to fury.", effects: {}, conditionalResolution: { checkFunction: "food_restored", checkInterval: "daily", successOutcomes: { happiness: +15, loyalty: +20, security: +5, custom: { type: "connect", eventId: "food_starvation_grace_success", delay: 0 } }, failureOnDeadline: true }, failurePenalty: { happiness: -35, loyalty: -30, security: -20, crime: +25, custom: { type: "connect", eventId: "food_starvation_grace_failed", delay: 0 } }, choices: [ { text: "Continue waiting and hoping", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "food_starvation_refused", category: "health", severity: "major", triggerChance: 0.0, regionType: "any", weight: 1, message: "The starving were turned away", description: "Your cruel words cut deep. The starving people disperse in despair and rage. Word spreads quickly of your heartless response, and many question your fitness to lead. Crime surges as desperate people take what they need to survive.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "food_starvation_deflected", category: "health", severity: "major", triggerChance: 0.0, regionType: "any", weight: 1, message: "The hungry were left waiting", description: "Your indifference leaves the people feeling abandoned. While not as harsh as outright refusal, your lack of action breeds resentment. The hungry disperse, but they remember that you turned away when they needed you most.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "food_starvation_persuade_failed", category: "health", severity: "major", triggerChance: 0.0, regionType: "any", weight: 1, message: "Your words fell on deaf ears", description: "Your pleas fall on deaf ears. The hungry mob grows angry at your empty promises. Some turn to theft and violence out of desperation. Your inability to help them has damaged your reputation severely.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "food_starvation_grace_success", category: "health", severity: "major", triggerChance: 0.0, regionType: "any", weight: 1, message: "Food supplies have arrived!", description: "Food supplies have arrived! The waiting people are overjoyed and deeply grateful that you kept your promise. Your word is proven trustworthy, and loyalty to your leadership strengthens.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "food_starvation_grace_failed", category: "health", severity: "major", triggerChance: 0.0, regionType: "any", weight: 1, message: "The promise was broken", description: "Four days have passed and no food has come. The people feel betrayed by your broken promise. Riots break out, trust in your leadership crumbles, and desperate violence spreads through the streets.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "blacksmith_arrival", category: "population", severity: "minor", triggerChance: 0.6, regionType: "any", weight: 8, fallbackChance: 0.25, message: "A traveling blacksmith seeks to establish a workshop", description: "A skilled blacksmith arrives at your gates, his wagon laden with tools and materials. He's heard of your growing settlement and wishes to establish his workshop here. 'I can forge weapons, repair armor, and provide valuable services,' he explains. 'All I need is a place to work and a roof over my head.'", effects: {}, choices: [ { text: "Welcome the blacksmith and offer support", outcomes: { happiness: +3, loyalty: +2, custom: { type: "connect", eventId: "blacksmith_welcomed", delay: 0 } } }, { text: "Attempt to kill the blacksmith and take his tools (Requires: Guard, Acting check)", condition: "has_guard", diceRoll: { required: true, context: "acting", target: 5 }, outcomes: { custom: { type: "connect", eventId: "blacksmith_assassination_success", delay: 0 } }, failureOutcomes: { happiness: -15, loyalty: -20, security: -10, custom: { type: "connect", eventId: "blacksmith_assassination_failed", delay: 0 } } }, { text: "Decline - the settlement isn't ready for a blacksmith", outcomes: { happiness: -2, custom: { type: "connect", eventId: "blacksmith_rejected", delay: 0 } } } ], conditions: { regionTriggers: ["has_housing_or_workplace"] } }, { id: "blacksmith_welcomed", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The blacksmith is welcomed", description: "The blacksmith thanks you warmly for your hospitality. 'I'll need a proper workshop to set up my forge,' he explains. 'Give me a blacksmith building and I'll serve your people well.'", effects: {}, choices: [ { text: "Promise to build a workshop soon", outcomes: { custom: { type: "connect", eventId: "blacksmith_build_requested", delay: 0 } } } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "blacksmith_build_requested", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, timeLimit: 3, message: "Blacksmith awaits his workshop", description: "The blacksmith waits patiently for his workshop to be built. He's given you three days to construct a blacksmith building. If you complete it in time, he'll begin his valuable work. If not, he may reconsider his decision to stay.", effects: {}, conditionalResolution: { checkFunction: "has_blacksmith_building", checkInterval: "daily", successOutcomes: { happiness: +8, loyalty: +5, custom: { type: "connect", eventId: "blacksmith_build_completed", delay: 0 } }, failureOnDeadline: true }, failurePenalty: { happiness: -5, loyalty: -3, description: "The blacksmith is disappointed that his workshop wasn't built in time.", custom: { type: "connect", eventId: "blacksmith_build_failed", delay: 0 } }, choices: [ { text: "Continue waiting", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "blacksmith_build_completed", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The blacksmith begins his work", description: "Delighted with his new workshop, the blacksmith fires up his forge and begins crafting. The sound of hammer on anvil rings through the settlement. He pledges his loyalty and skills to your cause.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "connect", eventId: "blacksmith_moved_in_notification", delay: 0 } } } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "blacksmith_build_failed", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The blacksmith is disappointed", description: "The blacksmith frowns as the three-day deadline passes. 'I understand that building takes time,' he says carefully. 'I can wait four more days, but after that, I'll need to find somewhere else that values my skills.'", effects: {}, choices: [ { text: "Promise to complete the workshop in four days", outcomes: { custom: { type: "connect", eventId: "blacksmith_grace_period", delay: 0 } } } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "blacksmith_grace_period", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, timeLimit: 4, message: "Final chance for the blacksmith", description: "This is your last opportunity. The blacksmith is packed and ready to leave if his workshop isn't completed within four days. His patience is wearing thin.", effects: {}, conditionalResolution: { checkFunction: "has_blacksmith_building", checkInterval: "daily", successOutcomes: { happiness: +6, loyalty: +4, custom: { type: "connect", eventId: "blacksmith_grace_success", delay: 0 } }, failureOnDeadline: true }, failurePenalty: { happiness: -10, loyalty: -8, description: "The blacksmith leaves, disappointed and spreading word of your broken promises.", custom: { type: "connect", eventId: "blacksmith_lost_forever", delay: 0 } }, choices: [ { text: "Continue waiting", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "blacksmith_grace_success", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The blacksmith stays after all", description: "Relief washes over the blacksmith's face as he sees his completed workshop. 'You kept your word in the end,' he says with a slight smile. 'That means something. Let's get to work.' He fires up the forge and begins his craft.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "connect", eventId: "blacksmith_moved_in_notification", delay: 0 } } } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "blacksmith_lost_forever", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The blacksmith departs", description: "With a shake of his head, the blacksmith hitches his wagon and departs. 'I gave you every chance,' he mutters. Word spreads of your failure to support skilled craftsmen, damaging your reputation among artisans.", effects: {}, choices: [ { text: "Continue", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "blacksmith_moved_in_notification", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Master Blacksmith has settled in", description: "Theron Ironforge has officially taken residence in your settlement. The sound of hammer on anvil now rings through the streets as he begins his work. The blacksmith building is now operational, and the citizens feel more secure knowing they have access to quality metalwork.", effects: {}, choices: [ { text: "Continue", outcomes: { custom: { type: "setFlag", flag: "blacksmithRecruited", value: true } } } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "blacksmith_assassination_success", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The blacksmith is dead", description: "Your guard quietly escorts the blacksmith to a secluded area under the pretense of showing him potential workshop locations. You convince the blacksmith that you're eager to help, putting him at ease. When his back is turned, your guard strikes swiftly and silently. The deed is done. His tools and materials are now yours.", effects: { gold: +50, happiness: -3 }, choices: [ { text: "Take his belongings", outcomes: { custom: { type: "comment", text: "TODO: Add blacksmith's tools and materials to player inventory when inventory system is implemented. Should include: forge hammer, anvil, tongs, ingots, and rare crafting materials." } } } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "blacksmith_assassination_failed", category: "population", severity: "moderate", triggerChance: 0.0, regionType: "any", weight: 1, message: "The assassination attempt failed", description: "Your acting wasn't convincing enough. The blacksmith senses something is wrong and becomes suspicious. When your guard makes his move, the blacksmith fights back, shouting for help. Witnesses arrive and the blacksmith escapes, spreading word of your treachery throughout the region. Your reputation is severely damaged.", effects: {}, choices: [ { text: "Deal with the consequences", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "blacksmith_rejected", category: "population", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The blacksmith moves on", description: "The blacksmith nods in understanding, though disappointment is clear in his eyes. 'I appreciate your honesty,' he says. 'Perhaps when your settlement is more established, we'll meet again.' He departs peacefully, seeking his fortune elsewhere.", effects: {}, choices: [ { text: "Wish him well", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "fishing_opportunity", category: "activity", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Fishing opportunity at the nearby stream", description: "You notice the stream near the settlement is teeming with fish. This could be a good opportunity to catch some food, though it requires steady hands and patience.", effects: {}, choices: [ { text: "Fish yourself (Dexterity Check)", diceRoll: { required: true, context: "fishing", target: 4 }, outcomes: { description: "Your patience pays off! You reel in a sizeable fish and return with fresh game from the streamside.", food: 15, custom: { type: "reward", items: [ { itemId: 'fresh_fish', recipient: 'player', options: { quantity: 1 } }, { itemId: 'duck_meat', recipient: 'shared', options: {} } ], message: "You brought back fresh catch from the stream!", showPopup: true } }, failureOutcomes: { description: "The fish were too quick and slipped away. You return empty-handed.", food: 5 } }, { text: "Have a citizen fish", outcomes: { custom: { type: "connect", eventId: "fishing_citizen_placeholder", delay: 0 } } }, { text: "Send a companion to fish", outcomes: { custom: { type: "connect", eventId: "fishing_companion_placeholder", delay: 0 } } } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "fishing_player_success", category: "activity", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "A successful catch!", description: "Your patience pays off! You reel in a sizeable fish and return with fresh game from the streamside.\n\nYou obtained: Fresh Fish, Duck", effects: {}, choices: [ { text: "Take the blade and head back", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "fishing_player_failure", category: "activity", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "The fish got away", description: "Despite your efforts, the fish prove too elusive today. Perhaps your timing was off, or the conditions weren't quite right. The stream remains peaceful, but your hands remain empty. Better luck next time.", effects: {}, choices: [ { text: "Head back empty-handed", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "quest_collect_firewood", category: "activity", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "A thick stand of fallen branches", description: "A cluster of fallen branches and dry logs lies just off the path. It looks like enough to restock the village firewood stores.", effects: {}, choices: [ { text: "Gather the firewood (Survival Check)", diceRoll: { required: true, context: "foraging", target: 4 }, outcomes: { description: "You stack the best logs and return with a solid haul of firewood.", wood: 20, custom: { type: "complete_quest", questId: "task_collect_firewood" } }, failureOutcomes: { description: "You only manage to gather a few usable branches.", wood: 6 } }, { text: "Leave it for now", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "quest_gather_mushrooms", category: "activity", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Mushrooms under the old oak", description: "A ring of mushrooms grows in the damp shade of an old oak. Some are perfect for cooking, others for medicine.", effects: {}, choices: [ { text: "Harvest the mushrooms (Survival Check)", diceRoll: { required: true, context: "foraging", target: 4 }, outcomes: { description: "You fill your satchel with fresh mushrooms and mark the spot for future gathering.", food: 12, custom: { type: "complete_quest", questId: "task_gather_mushrooms" } }, failureOutcomes: { description: "Most of the mushrooms crumble or turn out to be spoiled. You salvage a small handful.", food: 4 } }, { text: "Leave them be", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "quest_collect_grass", category: "activity", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Tall reeds along the creek", description: "Tall reeds sway beside a shallow creek. Bundles of these grasses make good bedding and roofing material.", effects: {}, choices: [ { text: "Cut the reeds (Survival Check)", diceRoll: { required: true, context: "foraging", target: 4 }, outcomes: { description: "You bundle the reeds into tidy stacks and bring them back to the village.", wood: 8, custom: { type: "complete_quest", questId: "task_collect_grass" } }, failureOutcomes: { description: "The reeds split or slip from your grasp. You return with only a few usable bundles.", wood: 3 } }, { text: "Come back later", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "quest_hunt_game", category: "activity", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Fresh tracks in the underbrush", description: "You spot fresh animal tracks leading into a quiet glade. A careful hunter could bring back a good haul of meat.", effects: {}, choices: [ { text: "Stalk the game (Dexterity Check)", diceRoll: { required: true, context: "hunting", target: 5 }, outcomes: { description: "You bring down a swift animal and return with plenty of fresh meat.", food: 20, custom: { type: "complete_quest", questId: "task_hunt_game" } }, failureOutcomes: { description: "The animal bolts before you can act. You only manage to scare up a small catch.", food: 6 } }, { text: "Save the hunt for another day", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "quest_collect_rare_wood", category: "activity", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "A stand of rare hardwood", description: "Deep in the woodland you find a stand of dense, dark timber. The wood is valuable but difficult to harvest cleanly.", effects: {}, choices: [ { text: "Harvest the hardwood (Strength Check)", diceRoll: { required: true, context: "foraging", target: 6 }, outcomes: { description: "You split the heavy timber into usable lengths and haul it back to town.", wood: 35, custom: { type: "complete_quest", questId: "task_collect_rare_wood" } }, failureOutcomes: { description: "The hardwood proves stubborn. You salvage only a few smaller pieces.", wood: 10 } }, { text: "Mark the trees and return later", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "fishing_citizen_placeholder", category: "activity", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Citizen fishing - Coming soon", description: "You consider delegating this task to one of your citizens. This feature is planned for a future update and will allow you to assign fishing tasks to capable citizens who can work while you handle other matters.", effects: {}, choices: [ { text: "Maybe another time", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "fishing_companion_placeholder", category: "activity", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Companion fishing - Coming soon", description: "You think about sending one of your companions to fish. This feature is planned for a future update and will allow your companions to use their skills for fishing tasks, potentially with better success rates based on their abilities.", effects: {}, choices: [ { text: "I'll handle it myself for now", outcomes: {} } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } }, { id: "scout_deployment_hub", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 20, message: "Deploy Scout for Reconnaissance", companionMetadata: { type: 'scout_ready', selectionMethod: 'first_available', count: 1, filters: { requiredRole: 'scout', excludeBuildingAssigned: true } }, description: "Your scout is ready to explore the surrounding regions. Where should they be deployed?", effects: {}, choices: [ { text: "Send to Woodlands", outcomes: { custom: { type: "scout_mission", eventId: "woodlands_discovery", baseTime: 3, location: "woodlands" } } } ], conditions: { minAbsenceDays: 0, regionTriggers: ["has_active_scout"] } }, { id: "woodlands_discovery", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Woodlands Scouted", description: "Your scout reports finding a dense forest area. In the clearing, they discovered a bandit camp with approximately 12 raiders. Most appear to be sleeping or resting. Signs indicate they've been raiding local merchant caravans. Wild game is abundant in the area.", effects: { custom: { type: "scout_loot", resources: { food: 12, wood: 6 } } }, choices: [ { text: "Explore deeper into Woodlands", outcomes: { custom: [ { type: "scout_update", location: "woodlands", discovery: { location: "woodlands", intel: "bandit_camp_12_sleeping", threat: "moderate" } }, { type: "scout_mission", eventId: "woodlands_deeper", baseTime: 1, location: "woodlands" } ] } }, { text: "Return Home", outcomes: { custom: [ { type: "scout_update", location: "woodlands", discovery: { location: "woodlands", intel: "bandit_camp_12_sleeping", threat: "moderate" } }, { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } ] } } ] }, { id: "woodlands_deeper", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Deep Woodlands", description: "Venturing deeper, your scout discovers hidden trails and old hunting paths. They find evidence of a larger bandit network - supply caches and meeting points. A rusty weapons cache is hidden beneath fallen logs.", effects: { custom: { type: "scout_loot", resources: { food: 15, wood: 8, iron: 3 } } }, choices: [ { text: "Return Home", outcomes: { custom: [ { type: "scout_update", location: "woodlands", discovery: { location: "woodlands_deeper", intel: "bandit_network_supply_caches", threat: "high" } }, { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } ] } } ] }, { id: "old_ruins_discovery", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Old Ruins Scouted", description: "Your scout reports finding ancient stone ruins overtaken by vegetation. Crumbling walls and collapsed archways suggest this was once a significant structure. Strange symbols are carved into weathered stones. The area appears abandoned but there are signs of recent campfires.", effects: {}, choices: [ { text: "Explore deeper into the ruins", outcomes: { custom: [ { type: "scout_update", location: "old_ruins", discovery: { location: "old_ruins", intel: "ancient_ruins_recent_activity", threat: "moderate" } }, { type: "scout_mission", eventId: "old_ruins_deeper", baseTime: 1, location: "old_ruins" } ] } }, { text: "Move to Mountain Pass", outcomes: { custom: [ { type: "scout_update", location: "old_ruins", discovery: { location: "old_ruins", intel: "ancient_ruins_recent_activity", threat: "moderate" } }, { type: "scout_mission", eventId: "mountain_pass_discovery", baseTime: 2, location: "mountain_pass" } ] } }, { text: "Move to Dark Woods", outcomes: { custom: [ { type: "scout_update", location: "old_ruins", discovery: { location: "old_ruins", intel: "ancient_ruins_recent_activity", threat: "moderate" } }, { type: "scout_mission", eventId: "dark_woods_discovery", baseTime: 2, location: "dark_woods" } ] } }, { text: "Return Home", outcomes: { custom: [ { type: "scout_update", location: "old_ruins", discovery: { location: "old_ruins", intel: "ancient_ruins_recent_activity", threat: "moderate" } }, { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } ] } } ] }, { id: "northern_roads_discovery", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Northern Roads Scouted", description: "Your scout reports finding well-traveled roads heading north. Fresh wagon tracks and horse droppings indicate regular merchant traffic. However, there are also signs of ambush sites - broken arrows and disturbed soil near chokepoints. A patrol schedule seems to be in effect.", effects: {}, choices: [ { text: "Follow the road to Mountain Pass", outcomes: { custom: [ { type: "scout_update", location: "northern_roads", discovery: { location: "northern_roads", intel: "merchant_route_ambush_sites", threat: "moderate" } }, { type: "scout_mission", eventId: "mountain_pass_discovery", baseTime: 2, location: "mountain_pass" } ] } }, { text: "Move to Border Fort", outcomes: { custom: [ { type: "scout_update", location: "northern_roads", discovery: { location: "northern_roads", intel: "merchant_route_ambush_sites", threat: "moderate" } }, { type: "scout_mission", eventId: "border_fort_discovery", baseTime: 2, location: "border_fort" } ] } }, { text: "Return Home", outcomes: { custom: [ { type: "scout_update", location: "northern_roads", discovery: { location: "northern_roads", intel: "merchant_route_ambush_sites", threat: "moderate" } }, { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } ] } } ] }, { id: "scout_return_home", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Scout Returns", description: "Your scout returns safely from their reconnaissance mission. They provide a detailed report of everything discovered during their exploration.", effects: {}, choices: [ { text: "Deploy to another location", outcomes: { custom: { type: "connect", eventId: "scout_deployment_hub", delay: 0 } } }, { text: "Dismiss the scout for now", outcomes: { custom: { type: "scout_update", location: null } } } ] }, { id: "old_ruins_deeper", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Ancient Catacombs", description: "Below the ruins, your scout discovers entrance to underground catacombs. Strange markings and old bones suggest this place may be cursed. Valuable artifacts are visible but retrieving them would be dangerous. Undead activity is possible.", effects: {}, choices: [ { text: "Move to Dark Woods", outcomes: { custom: { type: "scout_mission", eventId: "dark_woods_discovery", baseTime: 2, location: "dark_woods" } } }, { text: "Move to Mountain Pass", outcomes: { custom: { type: "scout_mission", eventId: "mountain_pass_discovery", baseTime: 2, location: "mountain_pass" } } }, { text: "Return Home", outcomes: { custom: { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } } } ] }, { id: "mountain_pass_discovery", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Mountain Pass Scouted", description: "Your scout climbs the high altitude pass. The elevation provides excellent vantage points. They spot iron ore deposits in the rocky cliffs and identify signs of a bandit lookout position. Recent rockslides make the path treacherous - avalanche danger is high.", effects: {}, choices: [ { text: "Survey the mining prospects", outcomes: { custom: { type: "scout_mission", eventId: "mountain_pass_deeper", baseTime: 1, location: "mountain_pass" } } }, { text: "Move to Northern Roads", outcomes: { custom: { type: "scout_mission", eventId: "northern_roads_discovery", baseTime: 2, location: "northern_roads" } } }, { text: "Return Home", outcomes: { custom: { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } } } ] }, { id: "mountain_pass_deeper", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Highland Caves", description: "Higher up the mountain, your scout finds cave systems that could serve as defensive positions or storage. Inside one cave, they discover evidence of recent occupation - possibly refugees or deserters hiding from authorities.", effects: {}, choices: [ { text: "Move to Border Fort", outcomes: { custom: { type: "scout_mission", eventId: "border_fort_discovery", baseTime: 2, location: "border_fort" } } }, { text: "Move to Old Ruins", outcomes: { custom: { type: "scout_mission", eventId: "old_ruins_discovery", baseTime: 2, location: "old_ruins" } } }, { text: "Return Home", outcomes: { custom: { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } } } ] }, { id: "river_crossing_discovery", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "River Crossing Scouted", description: "Your scout reaches the river ford. The wooden bridge appears structurally sound. Below, they observe smugglers using small boats to move goods under cover of darkness. The river itself is rich with fish. Safe crossing points are marked on both banks.", effects: {}, choices: [ { text: "Investigate smuggler operations", outcomes: { custom: { type: "scout_mission", eventId: "river_crossing_deeper", baseTime: 1, location: "river_crossing" } } }, { text: "Move to Woodlands", outcomes: { custom: { type: "scout_mission", eventId: "woodlands_discovery", baseTime: 2, location: "woodlands" } } }, { text: "Return Home", outcomes: { custom: { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } } } ] }, { id: "river_crossing_deeper", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "River Smuggler Network", description: "Following the smuggler trails, your scout discovers a small riverside camp. The smugglers appear to be trafficking illegal goods but seem non-hostile. They might be persuaded to pay tolls or provide information for the right price.", effects: {}, choices: [ { text: "Move to Abandoned Village", outcomes: { custom: { type: "scout_mission", eventId: "abandoned_village_discovery", baseTime: 2, location: "abandoned_village" } } }, { text: "Move to Dark Woods", outcomes: { custom: { type: "scout_mission", eventId: "dark_woods_discovery", baseTime: 2, location: "dark_woods" } } }, { text: "Return Home", outcomes: { custom: { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } } } ] }, { id: "abandoned_village_discovery", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Abandoned Village Scouted", description: "Your scout enters the deserted settlement. Buildings stand empty, doors left open as if people fled in haste. They find usable supplies - tools, furniture, preserved food. However, disturbing signs suggest disease may have driven the inhabitants away. Some refugees might still be hiding nearby.", effects: {}, choices: [ { text: "Search for survivors", outcomes: { custom: { type: "scout_mission", eventId: "abandoned_village_deeper", baseTime: 1, location: "abandoned_village" } } }, { text: "Move to Old Ruins", outcomes: { custom: { type: "scout_mission", eventId: "old_ruins_discovery", baseTime: 2, location: "old_ruins" } } }, { text: "Return Home", outcomes: { custom: { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } } } ] }, { id: "abandoned_village_deeper", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Village Outskirts", description: "In the outskirts, your scout finds a small group of survivors - families who avoided the disease by isolating themselves. They're desperate for protection and willing to relocate. The village well appears to be contaminated, explaining the outbreak.", effects: {}, choices: [ { text: "Move to River Crossing", outcomes: { custom: { type: "scout_mission", eventId: "river_crossing_discovery", baseTime: 2, location: "river_crossing" } } }, { text: "Move to Border Fort", outcomes: { custom: { type: "scout_mission", eventId: "border_fort_discovery", baseTime: 2, location: "border_fort" } } }, { text: "Return Home", outcomes: { custom: { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } } } ] }, { id: "border_fort_discovery", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Border Fort Scouted", description: "Your scout observes the military fortification from a distance. Garrison appears at half strength - approximately 25 soldiers. They're conducting regular patrols along the border. Enemy scouts have been spotted in the area. The fort's defensive positions are well-maintained.", effects: {}, choices: [ { text: "Monitor enemy activity", outcomes: { custom: { type: "scout_mission", eventId: "border_fort_deeper", baseTime: 1, location: "border_fort" } } }, { text: "Move to Northern Roads", outcomes: { custom: { type: "scout_mission", eventId: "northern_roads_discovery", baseTime: 2, location: "northern_roads" } } }, { text: "Return Home", outcomes: { custom: { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } } } ] }, { id: "border_fort_deeper", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Enemy Movements", description: "Maintaining surveillance, your scout identifies enemy patrol patterns and supply routes. The enemy force appears to be preparing for increased activity. Intelligence suggests they may be planning raids or troop movements in the coming weeks.", effects: {}, choices: [ { text: "Move to Mountain Pass", outcomes: { custom: { type: "scout_mission", eventId: "mountain_pass_discovery", baseTime: 2, location: "mountain_pass" } } }, { text: "Move to Abandoned Village", outcomes: { custom: { type: "scout_mission", eventId: "abandoned_village_discovery", baseTime: 2, location: "abandoned_village" } } }, { text: "Return Home", outcomes: { custom: { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } } } ] }, { id: "dark_woods_discovery", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Dark Woods Scouted", description: "Your scout ventures into the ominous forest. Unnatural fog clings to the ground. They report strange sounds and unsettling sensations. Ancient stone markers bear warnings in old tongues. Magical energy permeates the area - possibly beneficial herbs or dangerous curses.", effects: {}, choices: [ { text: "Investigate magical phenomena", outcomes: { custom: { type: "scout_mission", eventId: "dark_woods_deeper", baseTime: 1, location: "dark_woods" } } }, { text: "Move to Old Ruins", outcomes: { custom: { type: "scout_mission", eventId: "old_ruins_discovery", baseTime: 2, location: "old_ruins" } } }, { text: "Return Home", outcomes: { custom: { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } } } ] }, { id: "dark_woods_deeper", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Cursed Grounds", description: "Deeper in the woods, your scout finds a ritual site with fresh offerings. Strange creatures move in the shadows - neither fully animal nor spirit. The area radiates powerful magic that could be harnessed or should be avoided. A hermit's hut sits in a protected clearing.", effects: {}, choices: [ { text: "Move to River Crossing", outcomes: { custom: { type: "scout_mission", eventId: "river_crossing_discovery", baseTime: 2, location: "river_crossing" } } }, { text: "Move to Woodlands", outcomes: { custom: { type: "scout_mission", eventId: "woodlands_discovery", baseTime: 2, location: "woodlands" } } }, { text: "Return Home", outcomes: { custom: { type: "scout_mission", eventId: "scout_return_home", baseTime: 2, location: null } } } ] }, { id: "investigate_abandoned_cart", category: "scout", severity: "minor", triggerChance: 0.0, regionType: "any", weight: 1, message: "Abandoned Cart", description: "A broken merchant cart lies ahead, wheel shattered and goods scattered. Fresh claw marks gouge the wood. Blood stains the ground, still wet.", effects: {}, choices: [ { text: "Follow the blood trail", outcomes: { custom: { type: "reveal_map_node", mapId: "woodlands", nodeData: { id: "hidden_bandit_camp", defId: "bandit_camp", position: { x: 530, y: 410 }, icon: "Bin/Contents/Locations/Southwood_Forest/POI/Hideout_Day.png", size: 150, customClass: "revealed-node map-node--no-background", encounterId: "bandit_duo_fight" } } } }, { text: "Leave it alone", outcomes: { happiness: -1 } } ], conditions: { minAbsenceDays: 0, regionTriggers: [] } } ]>> <<set $randomEventTriggers = { // Population conditions "high_population_density": function(regionStats) { return regionStats.population > 150; }, "low_population": function(regionStats) { return regionStats.population < 50; }, // Economic conditions "high_wealth": function(regionStats) { return regionStats.gold > 500; }, "low_food_production": function(regionStats) { return regionStats.food < 100; }, "stable_food": function(regionStats) { return regionStats.food >= 150; }, "food_shortage": function(regionStats) { return regionStats.food < 50; }, "food_depleted": function(regionStats) { return regionStats.food === 0 || regionStats.food < 1; }, "food_restored": function(regionStats) { return regionStats.food > 0; }, // Security conditions "low_security": function(regionStats) { return regionStats.security < 40; }, "weak_governance": function(regionStats) { return regionStats.loyalty < 30; }, // Social conditions "low_happiness": function(regionStats) { return regionStats.happiness < 40; }, "high_taxation": function(regionStats) { // TODO: Add taxation system check return false; }, // Environmental conditions (placeholder functions) "dry_season": function(regionStats) { // TODO: Check game season return Math.random() < 0.3; }, "winter_season": function(regionStats) { // TODO: Check if winter return Math.random() < 0.25; }, "bad_weather": function(regionStats) { return Math.random() < 0.2; }, "good_weather": function(regionStats) { return Math.random() < 0.3; }, // Infrastructure conditions "poor_sanitation": function(regionStats) { // TODO: Check for sanitation buildings return regionStats.population > 100; // Assume higher pop = sanitation issues }, "overcrowding": function(regionStats) { return regionStats.population > 120; }, // Regional characteristics (mostly always true for Blackmoor) "border_region": function(regionStats) { return true; // Blackmoor is a border region }, "agricultural_region": function(regionStats) { return true; // Blackmoor has farms }, "mountainous_terrain": function(regionStats) { return true; // Blackmoor has varied terrain }, "trade_routes": function(regionStats) { return regionStats.gold > 200; // Assume wealth indicates trade }, "stable_region": function(regionStats) { return regionStats.happiness > 60 && regionStats.security > 50; }, // Activity-based triggers "mining_operations": function(regionStats) { return regionStats.stone > 50; // Has stone production }, "exploration_activity": function(regionStats) { return Math.random() < 0.4; // Random exploration }, "trade_dependent": function(regionStats) { return regionStats.gold > 300; }, // Reputation triggers "reputation_safety": function(regionStats) { return regionStats.happiness > 70 && regionStats.security > 60; }, "reputation_sanctuary": function(regionStats) { return regionStats.loyalty > 60; }, "pious_population": function(regionStats) { return regionStats.loyalty > 55 && regionStats.happiness > 50; }, "open_borders": function(regionStats) { return regionStats.security >= 45 && regionStats.loyalty >= 45; }, // Special conditions "magical_activity": function(regionStats) { return Math.random() < 0.1; // Rare magical events }, "foreign_contact": function(regionStats) { return Math.random() < 0.3; // Contact with outsiders }, "valuable_resources": function(regionStats) { return regionStats.gold > 400 || regionStats.stone > 100; }, "has_guard": function(regionStats) { const companions = State.variables.characterDatabase || []; return companions.some(c => c.cityRole === "guard" && !c.buildingAssignment); }, "has_general": function(regionStats) { const companions = State.variables.characterDatabase || []; return companions.some(c => c.cityRole === "general" && !c.buildingAssignment); }, "has_spy": function(regionStats) { const companions = State.variables.characterDatabase || []; return companions.some(c => c.cityRole === "spy" && !c.buildingAssignment); }, "has_physician": function(regionStats) { const companions = State.variables.characterDatabase || []; return companions.some(c => c.cityRole === "physician" && !c.buildingAssignment); }, "has_diplomat": function(regionStats) { const companions = State.variables.characterDatabase || []; return companions.some(c => c.cityRole === "diplomat" && !c.buildingAssignment); }, "effective_guard": function(regionStats) { const companions = State.variables.characterDatabase || []; const guard = companions.find(c => c.cityRole === "guard" && !c.buildingAssignment); return guard && guard.cityRoleEffectiveness >= 1.0; }, "effective_general": function(regionStats) { const companions = State.variables.characterDatabase || []; const general = companions.find(c => c.cityRole === "general" && !c.buildingAssignment); return general && general.cityRoleEffectiveness >= 1.0; }, "has_housing_or_workplace": function(regionStats) { if (!State.variables || !State.variables.areas) return false; const areaIds = ['area1', 'area2', 'area3']; const buildingTypes = ['house', 'cottage', 'workshop', 'farm', 'market', 'tavern']; for (const areaId of areaIds) { const area = State.variables.areas[areaId]; if (!area || !area.buildings) continue; for (const buildingId in area.buildings) { const building = area.buildings[buildingId]; if (buildingTypes.includes(building.type || building.buildingType)) { return true; } } } return false; }, "has_blacksmith_building": function(regionStats) { if (!State.variables || !State.variables.areas) return false; const areaIds = ['area1', 'area2', 'area3']; for (const areaId of areaIds) { const area = State.variables.areas[areaId]; if (!area || !area.buildings) continue; for (const buildingId in area.buildings) { const building = area.buildings[buildingId]; if ((building.type || building.buildingType) === 'blacksmith') { return true; } } } return false; }, "has_scout": function(regionStats) { const companions = State.variables.characterDatabase || []; return companions.some(c => c.cityRole === "scout" && !c.buildingAssignment); } }>>
<<silently>> <<run setup.predefinedCharacters = { "blacksmith_theron": { id: "blacksmith_theron", name: "Theron", lastName: "Ironforge", portrait: "portraits/theron_ironforge.png", stats: { attributes: { Strength: 18, Dexterity: 16, Intelligence: 14, Willpower: 16, Discipline: 17, Kindness: 12, Happiness: 14, Beauty: 11, Charisma: 13, Health: 95, Stamina: 94, Affection: 0 }, skills: { Academics: 11, Administration: 9, Medicine: 7, Science: 14, "Melee Combat": 15, "Ranged Combat": 8, Domestic: 11, Acrobatics: 8, Dancing: 6, Performance: 9, Gardening: 7, Survival: 13 }, states: { Fear: 2, Defiance: 6, Stress: 4, Respect: 15, Corruption: 1, Devotion: 7 } }, social: { relationshipStatus: "single", partnerId: null, children: [], traits: ["Diligent", "Hardworking"], familyId: null }, inventory: [ { id: 'blacksmith_hammer', itemId: 'blacksmith_hammer', name: "Blacksmith's Hammer" }, { id: 'iron_tongs', itemId: 'iron_tongs', name: 'Iron Tongs' }, { id: 'leather_apron', itemId: 'leather_apron', name: 'Leather Apron' } ], goldPool: 45, foodPool: 25, itemPreferences: { byItem: { 'blacksmith_hammer': 'keep', 'iron_tongs': 'keep', 'leather_apron': 'keep' }, byType: { 'tool': 'keep', 'weapon': 'keep', 'resource': 'neutral' }, byTag: { 'smithing': 'keep', 'crafting': 'keep', 'quest': 'keep' }, default: 'neutral', desperationThreshold: 20, luxuryThreshold: 70, minimumStockOverrides: { 'blacksmith_hammer': 1, 'iron_tongs': 1 } } } }>> <</silently>>
<<set $initialBuildings = { "area1": [ { id: "blackmoorcastle", name: "Blackmoor Castle", x: 27, y: 0, width: 23, height: 14, type: "operational_building", buildingType: "castle", condition: "operational", image: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/PlayerKeep/Player-Keep_Base.png", description: "The great Blackmoor Castle stands ready", status: { built: true, constructionComplete: true, interactable: true, enterable: true, entryPassage: "buildings-template", buildingInteriorId: "castle", entryActionLabel: "Enter Blackmoor Castle", enterDescription: "Return to the heart of your domain." } }, { id: "house_broken_1", name: "Collapsed House", x: 12, y: 18, width: 4, height: 4, type: "operational_building", buildingType: "house", condition: "broken", isBroken: true, repairCost: { gold: 80, wood: 25 }, image: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Houses/HousesT1.png", brokenImage: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Destroyed_Buildings/DHousesT1.png", description: "A ruined house, its roof caved in and walls crumbling. With some work, it could shelter people again.", status: { built: true, constructionComplete: true, interactable: true, enterable: false, isBroken: true } } ], "area2": [ { id: "house_broken_2", name: "Collapsed House", x: 20, y: 2, width: 4, height: 4, type: "operational_building", buildingType: "house", condition: "broken", isBroken: true, repairCost: { gold: 80, wood: 25 }, image: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Houses/HousesT1.png", brokenImage: "Bin/Contents/Locations/Player_Settlement/City_Builder/Buildings/Destroyed_Buildings/DHousesT1.png", description: "A ruined house, its roof caved in and walls crumbling. With some work, it could shelter people again.", status: { built: true, constructionComplete: true, interactable: true, enterable: false, isBroken: true } } ], "area3": [ ] }>>
<<set setup.preBattleChoiceDefinitions = [ { id: "silent_strike_sleeping", label: "Silent Strike", description: "Strike while they sleep.", conditions: { enemyStates: ["sleeping_exhausted", "very_drunk", "unconscious", "passed_out"] }, outcomes: { modifyEnemies: [ { target: "all", stat: "hp", modifier: -30, type: "percentage" } ], narrative: "You catch them in deep sleep - your blade finds its mark before they can even scream!" } }, { id: "throat_slit_sleeping", label: "Throat Slit", description: "Kill one silently.", conditions: { enemyStates: ["sleeping_exhausted", "unconscious"] }, outcomes: { modifyEnemies: [ { target: "random", count: 1, stat: "hp", modifier: -100, type: "percentage" } ], narrative: "One enemy will never wake up. The others stir at the commotion - prepare for battle!" } }, { id: "mock_drunk", label: "Mock & Intimidate", description: "Mock their drunken state.", conditions: { enemyStates: ["drunk", "very_drunk", "hungover", "severely_hungover"] }, outcomes: { modifyEnemies: [ { target: "all", stat: "attack", modifier: -3, type: "flat" }, { target: "all", stat: "defense", modifier: -2, type: "flat" } ], narrative: "Your mocking laughter rattles their drunken courage. They stumble forward, barely able to hold their weapons!" } }, { id: "poisoned_ale", label: "Poison Their Drinks", description: "Poison their drinks.", conditions: { enemyStates: ["celebrating", "drunk", "very_drunk", "relaxed"] }, outcomes: { modifyEnemies: [ { target: "all", stat: "hp", modifier: -15, type: "percentage" }, { target: "all", stat: "speed", modifier: -2, type: "flat" } ], narrative: "They gulp down the poisoned ale between laughs. Soon they're clutching their stomachs, weakened and slow." } }, { id: "sneak_attack_wounded", label: "Strike the Wounded", description: "Target their wounds.", conditions: { enemyStates: ["wounded", "injured", "wounded_triumphant", "seriously_wounded", "critical"] }, outcomes: { modifyEnemies: [ { target: "all", stat: "hp", modifier: -20, type: "percentage" } ], narrative: "You strike at their existing wounds. Blood flows freely as they cry out in pain!" } }, { id: "ambush_paranoid", label: "Exploit Paranoia", description: "Use their fear.", conditions: { enemyStates: ["paranoid", "hiding", "fleeing", "wounded_hiding"] }, outcomes: { modifyEnemies: [ { target: "all", stat: "accuracy", modifier: -10, type: "percentage" } ], narrative: "Their paranoia makes them erratic. They see threats everywhere and strike at nothing!" } }, { id: "wait_exhausted", label: "Wait for Exhaustion", description: "Wait for them to tire.", conditions: { enemyStates: ["exhausted_alert", "half_asleep", "tired"] }, outcomes: { modifyEnemies: [ { target: "all", stat: "speed", modifier: -2, type: "flat" }, { target: "all", stat: "evasion", modifier: -10, type: "percentage" } ], narrative: "You wait patiently as they struggle to stay alert. Their movements grow sluggish with exhaustion." } }, { id: "direct_assault", label: "Direct Assault", description: "Face them head-on.", conditions: { enemyStates: ["alert", "focused", "training", "hunting", "vigilant", "preparing", "planning_raid"] }, outcomes: { narrative: "You charge in with no advantage. A fair fight - if such a thing exists." } }, { id: "steal_supplies_absent", label: "Steal Supplies & Leave", description: "Steal and leave.", conditions: { enemyStates: ["absent"] }, outcomes: { skipBattle: true, lootModifier: 0.4, narrative: "You ransack their camp and disappear before they return. Victory without bloodshed." } }, { id: "sabotage_weapons", label: "Sabotage Weapons", description: "Damage their equipment.", conditions: { enemyStates: ["gambling", "arguing", "distracted", "domestic", "playing_dice"] }, outcomes: { modifyEnemies: [ { target: "all", stat: "attack", modifier: -4, type: "flat" } ], narrative: "While they're occupied, you nick their blades and loosen armor straps. They'll notice too late." } }, { id: "intimidate_weakened", label: "Intimidate Them", description: "Scare them off.", conditions: { enemyStates: ["miserable", "recovering", "stabilizing", "fleeing"] }, outcomes: { skipBattle: true, lootModifier: 0.2, narrative: "You step from the shadows with weapons drawn. They take one look at you and flee, leaving scraps behind." } }, { id: "challenge_duel", label: "Challenge to Duel", description: "Duel their leader.", conditions: { enemyStates: ["alert", "focused", "vigilant", "content"] }, outcomes: { modifyEnemies: [ { target: "strongest", count: 1, stat: "hp", modifier: -25, type: "percentage" } ], narrative: "You challenge their leader. Pride accepts. The duel is brief and brutal - their leader falls wounded!" } }, { id: "fight_default", label: "Fight", description: "Fight normally.", conditions: {}, outcomes: { narrative: "You advance into combat!" } } ]>>
<div class="fullscreenbackground"> <div class="gui_gametab"> <div class="right_container_tavern"> <!-- Top row --> <div class="top_row"> <button class="icon_button" onclick="openUIScreen('settlement_overview')"> <span class="tooltip">Castle Management</span> <img src="Bin/Contents/UI/Buttons/btn_city.png" alt="Castle"> </button> <button class="icon_button" onclick="openUIScreen('player')"> <img src="Bin/Contents/UI/Buttons/btn_companion.png" alt="Companion"> </button> <button class="icon_button" onclick="openUIScreen('army')"> <img src="Bin/Contents/UI/Buttons/btn_army.png" alt="Army"> </button> <button class="icon_button"> <img src="Bin/Contents/UI/Buttons/btn_quest.png" alt="Quest"> </button> </div> <!-- Bottom row --> <div class="bottom_row"> <button class="icon_button" onclick="openWorldMap()"> <img src="Bin/Contents/UI/Buttons/btn_map.png" alt="Map"> </button> <button class="icon_button" onclick="openUIScreen('game_prison')"> <img src="Bin/Contents/UI/Buttons/btn_dungeon.png" alt="Prisoner"> </button> <button class="icon_button" onclick="openUIScreen('player_inventory')"> <img src="Bin/Contents/UI/Buttons/btn_inventory.png" alt="Inventory"> </button> <button class="icon_button"> <img src="Bin/Contents/UI/Buttons/btn_message.png" alt="Message"> </button> </div> </div> <div class="left_container"> <div class="basic_information"> <div class="character_icon"> <img src="Bin\Contents\Characters\Human_Storyline\Story\Player\Frame.png" alt="human_avatar"> </div> <<include "characterstatusgui">> </div> <div class="left_container_buttons"> <button class="setting_button" onclick="openSettings()"> <img src="Bin/Contents/UI/Buttons/sbtn_settings.png" alt="settings"> </button> <button class="save_button" onclick="saveGame()"> <img src="Bin/Contents/UI/Buttons/sbtn_save.png" alt="save"> </button> <div class="return_home"> <button class="left_container_button"> <img src="Bin/Contents/UI/Buttons/sbtn_rhome.png" alt="return_home"> </button> </div> <button class="left_container_button" onclick="window.openCalendar(); return false;"> <img src="Bin/Contents/UI/Buttons/sbtn_skiptime.png" alt="time_skip"> </button> <div class="time_and_money"> <div class="dynamic_time_money"> <span class="time_value">12:00 AM | Monday</span> </div> </div> </div> </div> <div class="middle_container"> <div class="hear-me-out"> <div class="hear-me-out-text"><p>Actions</p></div> <div class="big-brain-time"> <div class="big-brain-time-choices" onclick="goToPassage('quest_board')">— Check the quest board.</div> <div class="big-brain-time-choices" >— Take a look around the tavern.</div> <div class="big-brain-time-choices" >— Go for a drink.</div> </div> </div> </div> </div> <div class="half-panel-background"> <div class="half-left-panel"> <div class="vivid-dream-board"><p>Blackmoor Tavern</p></div> <div class="vivid-dream-passage"> <p>The tavern is packed tonight—every bench occupied, candles flickering in salvaged sconces, the air thick with roasted meat and spilled ale. Laughter rises to the rafters, rough and real, mixing with the clink of mismatched cups and voices raised in conversation.</p> <p>The barkeep slides drinks across the scarred counter while serving girls weave between tables with trays of bread and stew. Someone's started singing in the corner, off-key and enthusiastic. The scorch marks on the walls catch the firelight, but no one's looking at them tonight—they're too busy leaning into the noise and warmth of a full house.</p> </div> <div class="image-holder"> <img id="scene-image" src="Bin/Contents/UI/Background_Images/Tavern.webp" alt="Tavern Scene"> </div> </div> </div> </div>
:: CompanionInteractions [setup] <<set setup.companionActions = { sabine_6: { interact: { header: "Interact", actions: [ { label: "Talk", type: "local", dialogue: "Sabine looks up from her ledger and offers a polite smile.", affectionDelta: 1, dailyCooldown: true, cooldownMessage: "Sabine has already shared her thoughts for today." }, { label: "Ask about her work", type: "local", dialogue: "She shares a few details about the latest trade routes.", minAffection: 10, affectionDelta: 1, dailyCooldown: true, cooldownMessage: "She has no new reports for today." }, { label: "Share the vivid dream", type: "story", target: "prologue_root", minAffection: 20 } ] } } }>>
<div class="fullscreenbackground"> <div class="gui_gametab_manual"> <<include mgui>> <div class="middle_container"> <div class="mc-middle-contents"> </div> </div> </div> <div class="building-interior-container"> <<nobr>> <div class="building-interior-left-panel"> <div class="bi-left-panel-content"> <div class="bi-section-header"><p>Level</p></div> <div class="bi-floor-buttons"></div> <div class="bi-section-header"><p>Location</p></div> <div class="bi-location-buttons"></div> </div> </div> <</nobr>> <div class="building-interior-middle-panel"> <div class="bi-floor-container"> <img class="bi-floor-image" src="" alt="Floor View"> </div> </div> <<nobr>> <div class="building-interior-right-panel"> <div class="bi-right-panel-content"> <div class="bi-section-header"><p>Actions</p></div> <div class="bi-action-buttons"> <p class="bi-empty-message">Select a location</p> </div> </div> </div> <</nobr>> </div> </div> <script> (function() { var buildingId = window.currentBuildingId || 'castle'; var initialFloor = 'main'; if (window.BuildingInteriorDefinitions && window.BuildingInteriorRenderer) { window.BuildingInteriorRenderer.initializeBuilding(buildingId, initialFloor); } if (window.disableGameButtons) { window.disableGameButtons(); } })(); </script>
<div class="fullscreenbackground"> <div class="gui_gametab"> <<include mgui>> <div class="middle_container"> <div class="hear-me-out"> <div class="hear-me-out-text"><p>Quest Test Actions</p></div> <div class="big-brain-time"> <div class="big-brain-time-choices" onclick="QuestTrackingSystem.giveQuest('test_quest_01')">— Give Quest: Merchant's Plea</div> <div class="big-brain-time-choices" onclick="QuestTrackingSystem.giveQuest('test_quest_02')">— Give Quest: Guard Duty</div> <div class="big-brain-time-choices" onclick="QuestTrackingSystem.completeObjective('test_quest_01', 0)">— Complete Objective 1 (Merchant)</div> <div class="big-brain-time-choices" onclick="QuestTrackingSystem.completeObjective('test_quest_01', 1)">— Complete Objective 2 (Merchant)</div> <div class="big-brain-time-choices" onclick="QuestTrackingSystem.completeObjective('test_quest_01', 2)">— Complete Objective 3 (Merchant)</div> <div class="big-brain-time-choices" onclick="QuestTrackingSystem.completeAllObjectives('test_quest_02')">— Complete ALL Objectives (Guard)</div> <div class="big-brain-time-choices" onclick="QuestTrackingSystem.completeQuest('test_quest_01')">— Complete Quest (Merchant)</div> <div class="big-brain-time-choices" onclick="QuestTrackingSystem.completeQuest('test_quest_02')">— Complete Quest (Guard)</div> <div class="big-brain-time-choices" onclick="console.log(QuestTrackingSystem.getState())">— Log Quest State (F12)</div> <div class="big-brain-time-choices" onclick="goToPassage('area1')">— Exit Test</div> </div> </div> </div> </div> <div class="half-panel-background"> <div class="half-left-panel"> <div class="vivid-dream-board"><p>Quest System Test</p></div> <div class="vivid-dream-passage"> <p>Test Flow:</p> <p>1. Give a quest</p> <p>2. Complete objectives one by one OR all at once</p> <p>3. Complete the quest</p> <p>4. Check console (F12) for state</p> </div> </div> </div> </div>
<<script>> (function() { if (State && State.variables) { if (State.variables._restSequenceInProgress && State.variables._restSequenceType !== 'sleep') { State.variables._restSequenceInProgress = false; State.variables._restSequenceType = null; } State.variables._restSequenceInProgress = true; State.variables._restSequenceType = 'sleep'; } const currentHour = State.variables.gameTime?.hour || 12; State.variables.preSleepTime = { hour: currentHour, day: State.variables.gameTime.day, month: State.variables.gameTime.month, year: State.variables.gameTime.year }; const sleepResult = window.SleepSystem.executeSleep(); State.variables.sleepResult = sleepResult; setTimeout(() => { window.SleepUI.showSleepSequence(sleepResult); }, 100); })(); <</script>> <div id="sleep-passage-container" style="opacity: 0;"> Sleeping... </div>
<div class="fullscreenbackground"> <div class="gui_gametab_battle"> <div class="right_container"> <<include "gui-button">> </div> <div class="left_container_430px"> <div class="basic_information"> <div class="character_icon"> <img src="Bin\Contents\Characters\Human_Storyline\Story\Player\Frame.png" alt="human_avatar"> </div> <<include "characterstatusgui">> </div> <div class="left_container_buttons"> <button class="left_container_button" onclick="openSettings()"> <img src="Bin/Contents/UI/Buttons/sbtn_settings.png" alt="settings"> </button> <button class="left_container_button" onclick="saveGame()"> <img src="Bin/Contents/UI/Buttons/sbtn_save.png" alt="save"> </button> <div class="return_home"> <button class="left_container_button"> <img src="Bin/Contents/UI/Buttons/sbtn_rhome.png" alt="return_home"> </button> </div> <button class="left_container_button" onclick="window.openCalendar(); return false;"> <img src="Bin/Contents/UI/Buttons/sbtn_skiptime.png" alt="time_skip"> </button> <div class="time_and_money"> <div class="dynamic_time_money"> <span class="time_value">12:00 AM | Monday</span> </div> </div> </div> </div> <div class="middle_container_130px"> <div class="hear-me-out-post-battle"> <div class="post-battle-actions-header"><p>Actions</p></div> <div class="post-battle-actions-list"> <div class="post-battle-action-choice" data-passage="area1">Leave</div> </div> </div> </div> <div class="post-battle-container"> <div class="post-battle-panels-container"> <!-- LEFT PANEL --> <div class="post-battle-panel left"> <<nobr>> <div class="result-emblem"> <<if $combatResult === 'victory'>> <div class="emblem-icon victory"></div> <<else>> <div class="emblem-icon defeat"></div> <</if>> </div> <div class="result-banner"> <<if $combatResult === 'victory'>> <div class="result-title victory">Victory!</div> <<else>> <div class="result-title defeat">Defeat</div> <</if>> </div> <div class="narrative-section"> <<nobr>> <div id="post-battle-narrative"> <<if $combatResult === 'victory'>> <p class="narrative-text">Your forces have prevailed. The enemy has been routed and their supplies now belong to you.</p> <p class="narrative-text">Soldiers gather the spoils while the wounded are tended to. It is time to return home.</p> <<else>> <p class="narrative-text">Your forces were overwhelmed. You managed to retreat from the battlefield, but at great cost.</p> <p class="narrative-text">The sounds of battle fade behind you as the remnants of your army limp toward home.</p> <</if>> </div> <</nobr>> </div> <</nobr>> </div> <!-- RIGHT PANEL --> <div class="post-battle-panel right"> <<nobr>> <<if $combatResult === 'victory'>> <!-- VICTORY: Show loot --> <div class="panel-content"> <div class="loot-header"><p>Available Loots</p></div> <div class="loot-section"> <div class="loot-grid" id="loot-grid"></div> </div> <div class="allies-section"> <div class="captured-slaves-header">Captured</div> <div class="allies-grid" id="allies-grid"></div> </div> <div class="resources-section"> <div class="resources-header">Recovered Resources</div> <div class="resources-grid" id="resources-grid"></div> </div> </div> <div class="action-section"> <button class="action-btn" id="collect-btn">Loot All</button> </div> <<else>> <!-- DEFEAT: Show unit cards --> <div class="panel-content"> <div class="defeat-header"> <p>Casualties</p> </div> <div class="post-battle-units-container" id="defeat-units-container"> <!-- 5 unit cards populated by JS --> </div> <div class="casualty-summary"> <div class="casualty-summary-title">Total Casualty</div> <div class="casualty-summary-row"> <div class="casualty-icon dead"></div> <span class="casualty-label">Dead:</span> <span class="casualty-value"><<= $combatCasualties.deaths || 0>></span> </div> <div class="casualty-summary-row"> <div class="casualty-icon wounded"></div> <span class="casualty-label">Wounded:</span> <span class="casualty-value"><<= $combatCasualties.wounded || 0>></span> </div> </div> </div> <div class="action-section"> <button class="action-btn" id="heal-all-btn" disabled>Heal All</button> <div class="bandage-usage" id="bandage-usage" style="display: none;"> <div class="bandage-icon"></div> <span class="bandage-text">Used 0 bandages</span> </div> </div> <</if>> <</nobr>> </div> </div> </div> </div> <<script>> $(document).ready(function() { var result = State.variables.combatResult; var narrativeContainer = document.getElementById('post-battle-narrative'); function escapeNarrative(value) { if (typeof window.escapePostBattleHtml === 'function') { return window.escapePostBattleHtml(value); } if (value === null || value === undefined) return ''; return String(value) .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } function normalizeNarrative(value) { if (!value) return null; if (Array.isArray(value)) { var filtered = value.filter(function(entry) { return entry !== null && entry !== undefined && String(entry).trim().length > 0; }); return filtered.length ? filtered : null; } if (typeof value === 'string' && value.trim().length === 0) { return null; } return value; } function resolvePostBattleNarrative(resultState) { if (typeof setup !== 'object' || !setup || !setup.presetEncounters) { return null; } var encounterData = State.variables.currentEncounterData || {}; var encounterId = State.variables.currentEncounterId || encounterData.id || null; if (!encounterId || !setup.presetEncounters[encounterId]) { return null; } var encounter = setup.presetEncounters[encounterId]; var narrative = encounter.postBattleNarrative; if (!narrative) return null; if (resultState === 'victory') { var tier = 'standard'; if (typeof window.getPostBattleVictoryTier === 'function') { tier = window.getPostBattleVictoryTier(State.variables.combatUnits || [], State.variables.initialCombatUnits || []); } var victoryBlock = narrative.victory; if (typeof victoryBlock === 'string' || Array.isArray(victoryBlock)) { return normalizeNarrative(victoryBlock); } victoryBlock = victoryBlock || {}; return normalizeNarrative(victoryBlock[tier] || victoryBlock.standard || victoryBlock.overwhelming || victoryBlock.barely || null); } if (resultState === 'defeat') { return normalizeNarrative(narrative.defeat || null); } return null; } var resolvedNarrative = resolvePostBattleNarrative(result); if (narrativeContainer && resolvedNarrative) { var paragraphs = Array.isArray(resolvedNarrative) ? resolvedNarrative : [resolvedNarrative]; narrativeContainer.innerHTML = paragraphs.map(function(text) { return '<p class="narrative-text">' + escapeNarrative(text) + '</p>'; }).join(''); } if (result === 'victory') { var loot = State.variables.combatLoot || {}; // Populate allies grid with captured slaves (male/female rows) var alliesGrid = document.getElementById('allies-grid'); var capturedSlaves = loot.capturedSlaves || {}; var slaveRoster = Array.isArray(capturedSlaves.roster) ? capturedSlaves.roster : []; // Count males and females from roster or use direct counts var maleCount = 0; var femaleCount = 0; if (slaveRoster.length > 0) { for (var s = 0; s < slaveRoster.length; s++) { var slave = slaveRoster[s]; if (slave.gender === 'female') { femaleCount++; } else { maleCount++; } } } else { maleCount = capturedSlaves.male || 0; femaleCount = capturedSlaves.female || 0; } // Male row if (maleCount > 0) { alliesGrid.innerHTML += '<div class="slave-row">' + '<div class="sr-slave-icons male"></div>' + '<span class="slave-name">Male</span>' + '<span class="slave-amount">' + maleCount + ' slaves</span>' + '</div>'; } // Female row if (femaleCount > 0) { alliesGrid.innerHTML += '<div class="slave-row">' + '<div class="sr-slave-icons female"></div>' + '<span class="slave-name">Female</span>' + '<span class="slave-amount">' + femaleCount + ' slaves</span>' + '</div>'; } var items = Array.isArray(loot.items) ? loot.items : []; var lootGrid = document.getElementById('loot-grid'); for (var i = 0; i < 5; i++) { var item = items[i]; if (item) { var rarity = item.rarity ? ' ' + item.rarity.toLowerCase() : ''; lootGrid.innerHTML += '<div class="loot-slot filled' + rarity + '">' + '<div class="loot-icon"></div>' + '<span class="loot-name">' + (item.name || item.id || 'Item') + '</span>' + '<span class="loot-qty">x' + (item.quantity || 1) + '</span>' + '</div>'; } else { lootGrid.innerHTML += '<div class="loot-slot empty"></div>'; } } var resourcesGrid = document.getElementById('resources-grid'); var gold = loot.gold || 0; var food = (loot.resources && loot.resources.food) ? loot.resources.food : 0; var population = (loot.resources && loot.resources.population) ? loot.resources.population : 0; if (gold > 0) { resourcesGrid.innerHTML += '<div class="resource-row">' + '<div class="rr-resource-icons gold"></div>' + '<span class="resource-name">Gold</span>' + '<span class="resource-amount gain">+' + gold + '</span>' + '</div>'; } if (food > 0) { resourcesGrid.innerHTML += '<div class="resource-row">' + '<div class="rr-resource-icons food"></div>' + '<span class="resource-name">Food</span>' + '<span class="resource-amount gain">+' + food + '</span>' + '</div>'; } if (population > 0) { resourcesGrid.innerHTML += '<div class="resource-row">' + '<div class="rr-resource-icons population"></div>' + '<span class="resource-name">Population</span>' + '<span class="resource-amount gain">+' + population + '</span>' + '</div>'; } if (gold === 0 && food === 0 && population === 0) { resourcesGrid.innerHTML += '<div class="resource-row">' + '<div class="rr-resource-icons"></div>' + '<span class="resource-name">No resources recovered</span>' + '<span class="resource-amount">-</span>' + '</div>'; } $('#collect-btn').on('click', function() { if (window.applyLootPayload) { window.applyLootPayload(State.variables.combatLoot); } window.returnToPrevious(); }); } else { var casualties = State.variables.combatCasualties || {}; var units = Array.isArray(casualties.units) ? casualties.units : []; var defeatContainer = document.getElementById('defeat-units-container'); // Always create 5 unit slots (same as pre-battle) for (var i = 0; i < 5; i++) { var unit = units[i]; // Create wrapper for unit + status icon var slotWrapper = document.createElement('div'); slotWrapper.className = 'defeat-unit-slot'; var unitElement = document.createElement('div'); unitElement.className = 'post-battle-unit'; if (unit && unit.name) { // FILLED SLOT - show companion var portraitDiv = document.createElement('div'); portraitDiv.className = 'post-battle-unit-portrait'; if (unit.portrait) { portraitDiv.style.backgroundImage = "url('" + unit.portrait + "')"; } unitElement.appendChild(portraitDiv); var nameDiv = document.createElement('div'); nameDiv.className = 'post-battle-unit-name'; nameDiv.textContent = unit.name; unitElement.appendChild(nameDiv); slotWrapper.appendChild(unitElement); // Add status icon below the unit card var statusIcon = document.createElement('div'); var iconClass = 'survived'; if (unit.status === 'killed') { iconClass = 'dead'; } else if (unit.status === 'wounded') { iconClass = 'wounded'; } else if (unit.status === 'flawless') { iconClass = 'flawless'; } statusIcon.className = 'unit-status-icon ' + iconClass; slotWrapper.appendChild(statusIcon); } else { // EMPTY SLOT unitElement.classList.add('empty-slot'); var emptyDiv = document.createElement('div'); emptyDiv.className = 'post-battle-unit-empty'; emptyDiv.textContent = 'Empty'; unitElement.appendChild(emptyDiv); slotWrapper.appendChild(unitElement); } defeatContainer.appendChild(slotWrapper); } // Heal All button logic var healAllBtn = document.getElementById('heal-all-btn'); var bandageUsageDiv = document.getElementById('bandage-usage'); if (healAllBtn) { var woundedCount = units.filter(function(u) { return u && u.status === 'wounded'; }).length; // Check bandage count in inventory var inventory = window.PlayerInventorySystem; var bandageCount = 0; if (inventory && typeof inventory.getItemCount === 'function') { bandageCount = inventory.getItemCount('field_bandage') || 0; } else if (inventory && typeof inventory.hasItem === 'function') { // Fallback: estimate count for (var b = 0; b < 100; b++) { if (inventory.hasItem('field_bandage', b + 1)) { bandageCount = b + 1; } else { break; } } } if (woundedCount === 0) { // No wounded units - button stays disabled healAllBtn.disabled = true; healAllBtn.title = ''; } else if (bandageCount < woundedCount) { // Not enough bandages - disabled with tooltip healAllBtn.disabled = true; healAllBtn.title = 'You don\'t have enough bandages to heal all'; } else { // Can heal all - enable button healAllBtn.disabled = false; healAllBtn.title = ''; } // Click handler healAllBtn.addEventListener('click', function() { if (this.disabled) return; var healed = 0; var unitsToHeal = units.filter(function(u) { return u && u.status === 'wounded'; }); unitsToHeal.forEach(function(unit) { if (inventory && typeof inventory.removeItem === 'function') { if (inventory.removeItem('field_bandage', 1)) { // Heal the unit unit.status = 'survived'; healed++; // Update unit HP if they have it if (unit.id && Array.isArray(State.variables.characterDatabase)) { var character = State.variables.characterDatabase.find(function(c) { return c && c.id === unit.id; }); if (character) { var maxHp = character.maxHp || (character.stats && character.stats.attributes && character.stats.attributes.Health) || 50; character.hp = maxHp; } } } } }); // Show bandage usage if (bandageUsageDiv && healed > 0) { var bandageText = bandageUsageDiv.querySelector('.bandage-text'); if (bandageText) { bandageText.textContent = 'Used ' + healed + ' bandage' + (healed > 1 ? 's' : ''); } bandageUsageDiv.style.display = 'flex'; } // Disable button after use this.disabled = true; this.classList.add('used'); // Update the status icons on the UI var statusIcons = document.querySelectorAll('.unit-status-icon.wounded'); statusIcons.forEach(function(icon) { icon.classList.remove('wounded'); icon.classList.add('survived'); }); }); } } var panelContent = document.querySelector('.post-battle-panel.right .panel-content'); var rightPanel = document.querySelector('.post-battle-panel.right'); if (panelContent && rightPanel) { function checkScrollPosition() { var isAtBottom = panelContent.scrollHeight - panelContent.scrollTop <= panelContent.clientHeight + 5; var canScroll = panelContent.scrollHeight > panelContent.clientHeight; if (!canScroll || isAtBottom) { rightPanel.classList.add('scrolled-bottom'); } else { rightPanel.classList.remove('scrolled-bottom'); } } panelContent.addEventListener('scroll', checkScrollPosition); checkScrollPosition(); } // Post-battle Leave choice handler $(document).on('click', '.post-battle-action-choice', function() { var passage = $(this).data('passage'); if (passage) { Engine.play(passage); } }); }); <</script>>
<div class="shop-container" data-shop-type=""> <<nobr>> <div class="shop-panels-container"> <!-- Left Panel: Items --> <div class="shop-panel left"> <div class="shop-header"> <p id="shop-title">Shop</p> </div> <div class="shop-tabs"> <button id="shop-tab-buy" class="shop-tab active">BUY</button> <button id="shop-tab-sell" class="shop-tab">SELL</button> </div> <div id="shop-items-container" class="shop-items mode-grid"> <div class="shop-empty-hint">Loading items...</div> </div> </div> <!-- Right Panel: Details --> <div class="shop-panel right"> <div id="shop-detail-panel" class="shop-detail-panel"> <!-- Item Showcase --> <div class="shop-detail-showcase"> <div class="shop-detail-image"></div> <div class="shop-detail-info"> <h3 class="shop-detail-name">Select an item</h3> <div class="shop-detail-type">Item</div> <div class="shop-detail-rarity common">Common</div> </div> </div> <!-- Description --> <div class="shop-detail-description"> <p>Select an item to view its description and properties.</p> </div> <!-- Stats --> <div class="shop-detail-stats"> <div class="shop-detail-stats-header">Properties</div> <div class="shop-detail-stat-row"> <span class="shop-detail-stat-name">Damage</span> <span class="shop-detail-stat-value">-</span> </div> <div class="shop-detail-stat-row"> <span class="shop-detail-stat-name">Weight</span> <span class="shop-detail-stat-value">-</span> </div> <div class="shop-detail-stat-row"> <span class="shop-detail-stat-name">Durability</span> <span class="shop-detail-stat-value">-</span> </div> </div> <!-- Purchase Section --> <div class="shop-detail-purchase"> <div class="shop-detail-price"> <span class="price-label">Price:</span> <div class="price-value-container"> <img class="price-icon" src=""> <span class="price-value">0</span> </div> </div> <button id="shop-action-btn" class="shop-action-button" disabled>Purchase</button> </div> </div> </div> </div> <!-- Purchase Stamp Animation --> <div class="shop-purchase-stamp">Purchased</div> <</nobr>> </div>
:: StoryEventTemplate <div class="fullscreenbackground"> <div class="gui_gametab_prologue"> <div class="right_container"> <<include "gui-button">> </div> <div class="left_container_395px"> <div class="basic_information"> <div class="character_icon" onclick="openUIScreen('player')"> <img src="Bin/Contents/Characters/Human_Storyline/Story/Player/Frame.png" alt="human_avatar"> </div> <<include "characterstatusgui">> </div> <div class="left_container_buttons"> <button class="left_container_button" data-gui-button="settings" data-tooltip="Settings" data-tooltip-placement="top" onclick="openSettings()"> <img src="Bin/Contents/UI/Buttons/sbtn_settings.png" alt="settings"> </button> <button class="left_container_button" data-gui-button="save" data-tooltip="Save" data-tooltip-placement="top" onclick="saveGame()"> <img src="Bin/Contents/UI/Buttons/sbtn_save.png" alt="save"> </button> <button class="left_container_button" data-gui-button="skip-time" data-tooltip="Skip 30 minutes" data-tooltip-placement="top" onclick="window.useSkipTime(); return false;"> <img src="Bin/Contents/UI/Buttons/sbtn_skiptime.png" alt="time_skip"> </button> <div class="time_and_money"> <div class="dynamic_time_money"> <span class="time_value">12:00 AM | Monday</span> </div> </div> <div class="return_home"> <button class="left_container_button" data-gui-button="return-home" data-tooltip-id="return-home" data-tooltip-placement="top" onclick="if (window.CompassNavigation && typeof window.CompassNavigation.returnToBlackmoorCastle === 'function') { window.CompassNavigation.returnToBlackmoorCastle(); } event.stopPropagation(); return false;"> <img src="Bin/Contents/UI/Buttons/sbtn_rhome.png" alt="return_home"> </button> </div> </div> </div> <div class="middle_container_150px"> <div class="hear-me-out"> <div class="hear-me-out-text"><p>Actions</p></div> <div class="big-brain-time" id="story-choices-container"> <!-- Dynamically populated by StoryEventRenderer --> </div> </div> </div> </div> <div class="half-panel-background"> <div class="half-left-panel"> <div class="vivid-dream-board"><p id="story-event-title">Story Event</p></div> <div class="vivid-dream-passage" id="story-description"> <!-- Dynamically populated by StoryEventRenderer --> </div> </div> </div> <div class="video-container"> <!-- Dynamically populated by StoryEventRenderer (video or image) --> </div> </div> <!-- Dice roll container --> <div id="diceContainer" class="dice-container"> <div class="roll-info">Roll for <span id="rollType">Attribute</span> Check</div> <div class="threshold-info">You need <span id="targetNumber">4</span>+ to succeed</div> <div class="scene"> <div id="dice" class="dice"> <div class="dice-face" data-value="1">1</div> <div class="dice-face" data-value="2">2</div> <div class="dice-face" data-value="3">3</div> <div class="dice-face" data-value="4">4</div> <div class="dice-face" data-value="5">5</div> <div class="dice-face" data-value="6">6</div> </div> </div> <div id="diceResult" class="dice-result"></div> </div>
<<set setup.prologueStoryEvents = [ { id: "prologue_root", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>Narrator: "You see the young servant girl tending to her duties in the study room, what will you do? I wonder..?" </p> <p2>*Choose an interaction at the action tab below*</p2>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-000.mp4", voiceSequence:['narrator_pss000'], choices: [ { text: "- Sneak up on her and take what's yours.", outcomes: { custom: { type: "connect", eventId: "prologue_force_100" } } }, { text: "- Approach her.", outcomes: { custom: { type: "connect", eventId: "prologue_approach_200_1" } } }, { text: "- Leave her be.", outcomes: { custom: { type: "connect", eventId: "prologue_leave_300" } } } ] }, { id: "prologue_leave_300", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>Narrator: "Well that's a shame, one would wonder where this dream would have gone. But, It matters not. It seems your dream is being interrupted." </p>`, mediaType: "image", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-300.jpg", voiceSequence: ['narrator_pss204_01'], choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "gss_000_1" } } } ] }, { id: "prologue_force_100", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>Narrator: "You felt a sudden urge rise within you, a need to satisfy your carnal desires. You sneaked up behind her and held her against the couch." </p> <p>Bella: "Ahhh! Get off me!" </p> <p>Narrator: "She struggles to get free from your hold, but to no avail she is now at your mercy." </p> <p>Bella: "M'Lord! It's you! have I done something wrong? Please let me go, You're hurting me. </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-100.mp4", voiceSequence: ['narrator_pss100_01', 'bella_pss100_01', 'narrator_pss100_02', 'bella_pss100_02'], choices: [ { text: "- Undress her.", outcomes: { custom: { type: "connect", eventId: "prologue_force_101" } } }, { text: "- Let her go.", outcomes: { custom: { type: "connect", eventId: "prologue_force_105" } } } ] }, { id: "prologue_force_101", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>Narrator: "Your hands moves without hesitation, stripping away her clothing piece by piece. Her protests grow weaker, a mix of fear and something else... What is this feeling rising within you? Power, perhaps? Or something darker?" </p> <p>Bella: *Trembling* "M-M'Lord, please... I beg you..." </p> <p>You: "Silence! You are my servant and you shall learn to obey!" </p> <p>Narrator: "She lays before you, naked, bare, and afraid." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-101.mp4", voiceSequence: ['narrator_pss101_01', 'bella_pss101_01', null, 'narrator_pss101_02'], choices: [ { text: "- Position her.", outcomes: { custom: { type: "connect", eventId: "prologue_force_102" } } } ] }, { id: "prologue_force_102", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>Narrator: "Your hands moves without hesitation, stripping away her clothing piece by piece. Her protests grow weaker, a mix of fear and something else... What is this feeling rising within you? Power, perhaps? Or something darker?" </p> <p>Bella: *Trembling* "M-M'Lord, please... I beg you..." </p> <p>You: "Silence! You are my servant and you shall learn to obey!" </p> <p>Narrator: "She lays before you, naked, bare, and afraid." </p> <p>Narrator: "You positioned her, getting her ready for what's to come" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-102.mp4", voiceSequence: ['narrator_pss102_01', 'bella_pss102_01', null, 'narrator_pss102_02', 'narrator_pss102_03'], voiceStartIndex: 4, choices: [ { text: "- Use her Pussy.", outcomes: { custom: { type: "connect", eventId: "prologue_force_103" } } }, { text: "- Enter her backdoor.", outcomes: { custom: { type: "connect", eventId: "prologue_force_104" } } } ] }, { id: "prologue_force_103", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>Narrator: "With a rough thrust, you enter her precious flower. She cries out in pain, her body tensing beneath you." </p> <p>Bella: *Sobbing* "Oh, it hurts... Please, stop..." </p> <p>You: "Silence! you'll learn to love it wrench!" </p> <p>Narrator: "You continue, driven by a mix of lust and dominance, ignoring her pleas. The sound of her cries fills the room as you take what you want." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-103.mp4", voiceSequence: ['narrator_pss103_01', 'bella_pss103_01', null, 'narrator_pss103_02'], choices: [ { text: "- Manual Controls.", outcomes: { custom: { type: "manualControls", position: "vaginal", eventId: "mc-bella-01" } } }, { text: "- Skip.", outcomes: { custom: { type: "connect", eventId: "gss_000_1" } } } ] }, { id: "prologue_force_104", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>Narrator: "Deciding to take a more painful route, you position yourself behind Bella and force your way into her tight anus. She screams in agony, her body convulsing." </p> <p>Bella: *Wailing* "No! Please, not there! It's un-natural! It's too much!" </p> <p>You: "You like it rough, don't you? Take it!" </p> <p>Narrator: "You thrust mercilessly, relishing in her suffering. The room echoes with her cries of pain and despair." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-104.mp4", voiceSequence: ['narrator_pss104_01', 'bella_pss104_01', null, 'narrator_pss104_02'], choices: [ { text: "- Manual Controls.", outcomes: { custom: { type: "manualControls", position: "anal", eventId: "mc-bella-anal" } } }, { text: "- Skip.", outcomes: { custom: { type: "connect", eventId: "gss_000_1" } } } ] }, { id: "prologue_force_105", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>Bella: "Ahhh! Get off me!" </p> <p>Narrator: "She struggles to get free from your hold, but to no avail she is now at your mercy." </p> <p>Bella: "M'Lord! It's you! have I done something wrong? Please let me go, You're hurting me." </p> <p>Narrator: "Your grip loosens, it seems sense and compassion has return to you." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-100.mp4", voiceSequence: [null, null, null, 'narrator_pss105_01'], voiceStartIndex: 3, choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "prologue_force_106" } } } ] }, { id: "prologue_force_106", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>You: "Go... just... get away from here.." </p> <p>Narrator: "She gasps for breath, and quickly she scrambles to her feet" </p> <p>Bella: "Y-Yes, M'Lord. Thank you..." </p> <p>Narrator: "She rushes out of the room, leaving you alone, contemplating about what you have attempted to do. It matters not, seems your dream is being interrupted." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-100.mp4", voiceSequence: [null, 'narrator_pss106_01', 'bella_pss106_01', 'narrator_pss106_02'], choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "gss_000_1" } } } ] }, { id: "prologue_approach_200_1", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>Bella: "Good Evening M'lord! Is there anything I could help you with?" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-200.mp4", voiceSequence: ['bella_pss200_01'], choices: [ { text: "- Ask her how she's been settling into her new role.", outcomes: { custom: { type: "connect", eventId: "prologue_approach_200_2" } } } ] }, { id: "prologue_approach_200_2", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>Bella: "Good Evening M'lord! Is there anything I could help you with?" </p> <p>You: "Good Evening Bella, how have you been finding your newfound servitude here?" </p> <p>Bella: "Oh, thank you for asking, M'lord. It's been... " *pauses, blushing slightly...* "quite an adjustment, but I'm learning." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-200.mp4", voiceSequence: ['bella_pss200_01', null, 'bella_pss200_02'], voiceStartIndex: 1, choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "prologue_approach_201" } } } ] }, { id: "prologue_approach_201", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>Narrator: "Bella's cheeks flush, hinting at an attraction she might be harboring. She seems more flustered than just the normal nervousness of a new role." </p> <p>Bella: "Well if there's nothing else my Lord, Might I continue on with my duties?" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-201.mp4", voiceSequence: ['narrator_pss201_01', 'bella_pss201_01'], choices: [ { text: "- Ask for Oral sex.(Attractive Roll)", diceRoll: { required: true, target: 4, context: "seduction" }, outcomes: { custom: { type: "connect", eventId: "prologue_approach_202_A1_success" } }, failureOutcomes: { custom: { type: "connect", eventId: "prologue_approach_202_A2_failed" } } }, { text: "- Ask for Oral sex. (Lie)", diceRoll: { required: true, target: 5, context: "charisma" }, outcomes: { custom: { type: "connect", eventId: "prologue_approach_202_B1_success" } }, failureOutcomes: { custom: { type: "connect", eventId: "prologue_approach_202_B2_failed" } } }, { text: "- Let her return to her duties.", outcomes: { custom: { type: "connect", eventId: "prologue_approach_202_C1" } } } ] }, { id: "prologue_approach_202_A1_success", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>You: *Smirks and leans in confidently* "Actually, there's something I've been craving lately... the soft, skilled touch of someone as lovely as you. Would you be willing to use your talents to give me a little... oral pleasure?" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-202.mp4", choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "prologue_approach_203" } } } ] }, { id: "prologue_approach_202_A2_failed", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>You: *Leans in slightly, trying to sound confident* "You see, my manhood has been aching for some wetness and I don't know if... you might suck it off? </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-202.mp4", choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "prologue_approach_204" } } } ] }, { id: "prologue_approach_202_B1_success", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>You: *Smirks and leans in confidently* "Bella, you see, the previous servant and I had an agreement. She would always provide me with oral pleasure whenever I desired it. I believe it's only fair that you continue this tradition." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-202.mp4", choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "prologue_approach_205" } } } ] }, { id: "prologue_approach_202_B2_failed", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>You: *Leans in slightly, trying to sound confident* "Bella, you see, the previous servant and I had an agreement. She would always provide me with oral pleasure whenever I desired it. I believe it's only fair that you continue this tradition." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-202.mp4", choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "prologue_approach_204" } } } ] }, { id: "prologue_approach_202_C1", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>You: "That's all Bella, It is a pleasure meeting your acquaintance." </p> <p>Bella: "Oh, certainly my Lord! Like wise." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-206.mp4", voiceSequence: [null, 'bella_pss204_01'], choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "prologue_leave_300" } } } ] }, { id: "prologue_approach_203", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>Narrator: "Her eyes widen slightly, but a spark of excitement flickers through them. She bites her lower lip and nods slowly." </p> <p>Bella: "If it would bring you pleasure, M'lord. I would be delighted to serve you in that way." </p> <p>Narrator: "Bella's cheeks flush with a deep red as she leans in closer, her submission clear. The atmosphere grows charged with anticipation and desire as she begins to fulfill your request, her actions driven by a combination of duty and her own hidden desire." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-203A.mp4", voiceSequence: ['narrator_pss202a_01', 'bella_pss202a_01', 'narrator_pss202a_02'], choices: [ { text: "- Manual Controls.", outcomes: { custom: { type: "manualControls", position: "bj", eventId: "mc-bella-bj" } } }, { text: "- Skip.", outcomes: { custom: { type: "connect", eventId: "gss_000_1" } } } ] }, { id: "prologue_approach_204", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>Narrator: "Her eyes widen, judgement and disgust flickers through them. She steps away slowly. " </p> <p>Bella: "How dare you! I am not some cheap whore for your amusement! Now if there isn't anything else I must get back to my duties!" </p> <p>Narrator: "Hilarious! It seems Bella's confidence and defiance are evident as she rejects your request, Leaving you with no choice but to accept her refusal and to leave the room. But, It matters not. It seems your dream is being interrupted."</p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-204A.mp4", voiceSequence: ['narrator_pss202b_01', 'bella_pss202b_01', 'narrator_pss202b_02'], choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "gss_000_1" } } } ] }, { id: "prologue_approach_205", category: "story", storyArc: "bella_introduction", message: "Vivid Dream", description: `<p>Narrator: "Her eyes widen in surprise, but she hesitates for a moment before agreeing with the request." </p> <p>Bella: "If that's what was done before, then... I suppose I can do the same. If it pleases you, M'Lord." </p> <p>Narrator: "Bella's compliance is tinged with a hint of uncertainty, but she decides to follow in the footsteps of her predecessor. She kneels before you, ready to fulfill your request." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Prologue/PSS-205.mp4", voiceSequence: ['narrator_pss203_01', 'bella_pss202a_01', 'narrator_pss203_02'], choices: [ { text: "- Manual Controls.", outcomes: { custom: { type: "manualControls", position: "bj", eventId: "mc-bella-bj" } } }, { text: "- Skip.", outcomes: { custom: { type: "connect", eventId: "gss_000_1" } } } ] }, { id: "gss_000_1", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Guard: "My Lord! My Lord! You've got to wake up!" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-001.mp4", voiceSequence: ['guard_gs_000_01'], choices: [ { text: "- Wake up.", outcomes: { custom: { type: "connect", eventId: "gss_001" } } }, { text: "- Ignore and keep sleeping.", outcomes: { custom: { type: "connect", eventId: "gss_000_2" } } } ] }, { id: "gss_000_2", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Guard: "My Lord! My Lord! You've got to wake up!" </p> <p>Guard: "Please My Lord this is urgent!" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-002.mp4", voiceSequence: [null, 'guard_gs_000_02'], voiceStartIndex: 1, choices: [ { text: "- Continue sleeping.", outcomes: { custom: { type: "connect", eventId: "gss_000_3" } } }, { text: "- Maybe I should wake up.", outcomes: { custom: { type: "connect", eventId: "gss_001" } } } ] }, { id: "gss_000_3", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Guard: "My Lord! My Lord! You've got to wake up!" </p> <p>Guard: "Please My Lord this is urgent!" </p> <p>Guard: "My Lord, Open the door! We are under Attack!" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-001.mp4", voiceSequence: [null, null, 'guard_gs_000_03'], voiceStartIndex: 2, choices: [ { text: "- Fuck it, sleep is more important.", outcomes: { custom: { type: "connect", eventId: "mainmenu" } } }, { text: "- Ok... I should really wake up.", outcomes: { custom: { type: "connect", eventId: "gss_001" } } } ] }, { id: "gss_001", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Guard: "My Lord! My Lord! You've got to wake up!" </p> <p>Guard: "Please My Lord this is urgent!" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-002.mp4", voiceSequence: [null, 'guard_gs_000_02'], voiceStartIndex: 1, choices: [ { text: "- Sit up.", outcomes: { custom: { type: "connect", eventId: "gss_002" } } } ] }, { id: "gss_002", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Guard: "My Lord! My Lord! You've got to wake up!" </p> <p>Guard: "Please My Lord this is urgent!" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-003.mp4", voiceSequence: null, choices: [ { text: "- Open the door.", outcomes: { custom: { type: "connect", eventId: "gss_01_004" } } } ] }, { id: "gss_01_004", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Roderic Kain: "My Lord! My Lord! You've got to wake up!" </p> <p>Roderic Kain: "Please My Lord this is urgent!" </p> <p>Roderic Kain: "Excuse the intrusion, My Lord. But We are under attack! Your Father Has Instructed me to bring you and the family to the cellar." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-004.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-004_Idle.mp4", voiceStartIndex: 2, voiceSequence: [null, null, 'gs_01_004'], choices: [ { text: "- What? Slow down Roderic. What is going on?", outcomes: { custom: { type: "connect", eventId: "gss_01_005" } } }, { text: "- Where is my Father", outcomes: { custom: { type: "connect", eventId: "gss_01_006" } } } ] }, { id: "gss_01_005", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Roderic Kain: "My Lord! My Lord! You've got to wake up!" </p> <p>Roderic Kain: "Please My Lord this is urgent!" </p> <p>Roderic Kain: "Excuse the intrusion, My Lord. But We are under attack! Your Father Has Instructed me to bring you and the family to the cellar." </p> <p>You: "What? Slow down Roderic. What is going on?" </p> <p>Roderic Kain: "Those bastards! of Clan Vargund and their band of filth! They crept in under the cover of night, pillaging our outer villages before we could even sound the alarm. Now they're right outside our castle gates... Gods, the lower villages are already burning!" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-005.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-004_Idle.mp4", voiceSequence: [null, null, null, null, 'gs_01_005'], voiceStartIndex: 4, choices: [ { text: "- Where is my Father?", outcomes: { custom: { type: "connect", eventId: "gss_01_006" } } }, { text: "- Where are my Mother and Sister?", outcomes: { custom: { type: "connect", eventId: "gss_01_007" } } } ] }, { id: "gss_01_006", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Roderic Kain: "My Lord! My Lord! You've got to wake up!" </p> <p>Roderic Kain: "Please My Lord this is urgent!" </p> <p>Roderic Kain: "Excuse the intrusion, My Lord. But We are under attack! Your Father Has Instructed me to bring you and the family to the cellar." </p> <p>You: "What?! Where is my Father?" </p> <p>Roderic Kain: "Your father's holding them off outside the gates for now, but there are too many of them. We must get you and the family to safety." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-006.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-004_Idle.mp4", voiceSequence: [null, null, null, null, 'gs_01_006'], voiceStartIndex: 4, choices: [ { text: "- Get Theron! We need to help them!", outcomes: { custom: { type: "connect", eventId: "gss_01_008" } } }, { text: "- Defend the Castle Door. I'll find my Mother and Sister.", outcomes: { custom: { type: "connect", eventId: "gss_01_009A", giveQuest: "quest_find_family" } } } ] }, { id: "gss_01_007", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Roderic Kain: "My Lord! My Lord! You've got to wake up!" </p> <p>Roderic Kain: "Please My Lord this is urgent!" </p> <p>Roderic Kain: "Excuse the intrusion, My Lord. But We are under attack! Your Father Has Instructed me to bring you and the family to the cellar." </p> <p>You: "Where are my Mother and Sister?" </p> <p>Roderic Kain: "I... I don't know, I haven't looked for them." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-007.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-004_Idle.mp4", voiceSequence: [null, null, null, null, 'gs_01_007'], voiceStartIndex: 4, choices: [ { text: "- Defend the Castle Door. I'll find my Mother and Sister.", outcomes: { custom: { type: "connect", eventId: "gss_01_009A", giveQuest: "quest_find_family" } } } ] }, { id: "gss_01_008", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Roderic Kain: "My Lord! My Lord! You've got to wake up!" </p> <p>Roderic Kain: "Please My Lord this is urgent!" </p> <p>Roderic Kain: "Excuse the intrusion, My Lord. But We are under attack! Your Father Has Instructed me to bring you and the family to the cellar." </p> <p>You: "Get Theron! We need to help them!" </p> <p>Roderic Kain: "But my Lord! Your father has given us clear orders." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-008.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-004_Idle.mp4", voiceSequence: [null, null, null, null, 'gs_01_008'], voiceStartIndex: 4, choices: [ { text: "- Alright fine. Let's find my Mother and Sister.", outcomes: { custom: { type: "connect", eventId: "gss_01_009B", giveQuest: "quest_find_family" } } }, { text: "- Fuck those orders!", outcomes: { custom: { type: "connect", eventId: "gss_01_008_2", giveQuest: "quest_aid_father" } } } ] }, { id: "gss_01_008_2", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Roderic Kain: "My Lord! My Lord! You've got to wake up!" </p> <p>Roderic Kain: "Please My Lord this is urgent!" </p> <p>Roderic Kain: "Excuse the intrusion, My Lord. But We are under attack! Your Father Has Instructed me to bring you and the family to the cellar." </p> <p>You: "Get Theron! We need to help them!" </p> <p>Roderic Kain: "But my Lord! Your father has given us clear orders." </p> <p>You: "Fuck those orders! My people are dying, I'm not going to hide with the women. Get Theron and meet me at the Castle Door. THAT'S AN ORDER!" </p> <p>Roderic Kain: "Understood, My Lord." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-008.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-004_Idle.mp4", voiceStartIndex: 6, voiceSequence: [null, null, null, null, null, null, 'gs_01_009'], choices: [ { text: "- Leave.", outcomes: { custom: { type: "enterBuildingInterior", buildingInteriorId: "castle", eventId: "gss_02_001" } } }, ] }, { id: "gss_01_009A", category: "story", storyArc: "guard_awakening", message: "Bedroom", description: `<p>Roderic Kain: "My Lord! My Lord! You've got to wake up!" </p> <p>Roderic Kain: "Please My Lord this is urgent!" </p> <p>Roderic Kain: "Excuse the intrusion, My Lord. But We are under attack! Your Father Has Instructed me to bring you and the family to the cellar." </p> <p>You: "Get Theron and Hold the Castle Door. I'll find my Mother and Sister." </p> <p>Roderic Kain: "Understood, My Lord." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-009.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-009_Idle.mp4", voiceSequence: [null, null, null, null, 'gs_01_009'], voiceStartIndex: 4, choices: [ { text: "- Leave.", outcomes: { custom: { type: "enterBuildingInterior", buildingInteriorId: "castle", giveQuest: "quest_find_family" } } } ] }, { id: "gss_01_009B", category: "story", storyArc: "guard_awakening", message: "Bedroom", description: `<p>Roderic Kain: "My Lord! My Lord! You've got to wake up!" </p> <p>Roderic Kain: "Please My Lord this is urgent!" </p> <p>Roderic Kain: "Excuse the intrusion, My Lord. But We are under attack! Your Father Has Instructed me to bring you and the family to the cellar." </p> <p>You: "Get Theron and Hold the Castle Door. I'll find my Mother and Sister." </p> <p>Roderic Kain: "Understood, My Lord." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-009.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-01-009_Idle.mp4", voiceSequence: [null, null, null, null, 'gs_01_009'], voiceStartIndex: 4, choices: [ { text: "- Leave.", outcomes: { custom: { type: "enterBuildingInterior", buildingInteriorId: "castle", giveQuest: "quest_find_family" } } } ] }, { id: "gss_02_001", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Roderic Kain: "What are your orders, My Lord?" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-001.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-Idle.mp4", voiceSequence: ['gs_02_001'], choices: [ { text: "- Let's go.", outcomes: { custom: { type: "connect", eventId: "gss_02_002" } } }, ] }, { id: "gss_02_002", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Theron Fenric: "By the Gods.." </p> <p>Narrator: "What a sight to behold! Surely you've not forgotten me. Now... Before you start rushing in, how about you scout your surroundings you may have missed something." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-002.mp4", voiceSequence: ['gs_02_002_sp5', 'gs_02_002_sp0'], choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "gss_02_003" } } } ] }, { id: "gss_02_003", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Theron Fenric: "By the Gods.." </p> <p>Narrator: "What a sight to behold! Surely you've not forgotten me. Now... Before you start rushing in, how about you scout your surroundings you may have missed something." </p> <p>Roderic Kain: "Over there! My Lord! Your Father is surrounded!" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-003.mp4", voiceSequence: [null, null, 'gs_02_003'], voiceStartIndex: 2, choices: [ { text: "- Take a look around.", outcomes: { custom: { type: "connect", eventId: "gss_02_004" } } }, { text: "- There's no time to waste!", outcomes: { custom: { type: "connect", eventId: "gss_02_003a" } } } ] }, { id: "gss_02_003a", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>You: "Quickly! to the aid of my Father!" </p> <p>Narrator: "Alright then just ignore the entire battlefield. Very smart of you." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-003A.mp4", voiceSequence: [null, 'gs_02_003a'], voiceStartIndex: 1, choices: [ { text: "- Make your way down.", outcomes: { custom: { type: "connect", eventId: "gss_02_013" } } } ] }, { id: "gss_02_004", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Narrator: "A wise decision, it seems a little birdie is in need. Perhaps saving her could give you an upper hand in the fights to come." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-004.mp4", voiceSequence: ['gs_02_004'], choices: [ { text: "- Save her.", outcomes: { custom: { type: "connect", eventId: "gss_02_005a" } } }, { text: "- Father is more important. (Sabine Orvenn will die)", outcomes: { custom: { type: "connect", eventId: "gss_02_013", setState: { sabineAlive: false, sabineInThroneRoom: false } } } } ] }, { id: "gss_02_005a", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>*You and your followers made your way down.*</p> <p>Narrator: "Hurry now... a moment more and she'll die" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-005.mp4", voiceSequence: [null, 'gs_02_005'], voiceStartIndex: 1, choices: [ { text: "- Get his attention!", outcomes: { custom: { type: "connect", eventId: "gss_02_008" } } }, { text: "- Pick and Throw a rock at him (Dexterity Roll)", diceRoll: { required: true, target: 4, context: "dexterity" }, outcomes: { custom: { type: "connect", eventId: "gss_02_009" } }, failureOutcomes: { custom: { type: "connect", eventId: "gss_02_006a" } } } ] }, { id: "gss_02_005b", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>You: "Quickly! to the aid of my Father!" </p> <p>Narrator: "Alright then just ignore the entire battlefield. Very smart of you." </p> <p>Narrator: "Hurry now... a moment more and she'll die" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-005.mp4", voiceSequence: [null, 'gs_02_003a', null, null, 'gs_02_005'], voiceStartIndex: 1, choices: [ { text: "- Continue Onward!", outcomes: { custom: { type: "connect", eventId: "gss_02_006b" } } }, { text: "- Save her!", outcomes: { custom: { type: "connect", eventId: "gss_02_005a" } } } ] }, { id: "gss_02_006a", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: ` <p>Narrator: "Hurry now... a moment more and she'll die" </p> <p>You: "*Hesitantly* Hey! You... You..." </p> <p>Narrator: "What was that? Such a pathetic attempt, And now she's dead." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-006.mp4", voiceSequence: [null, null, 'gs_02_006'], voiceStartIndex: 2, choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "gss_02_007" } } } ] }, { id: "gss_02_006b", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>You: "Quickly! to the aid of my Father!" </p> <p>Narrator: "Alright then just ignore the entire battlefield. Very smart of you." </p> <p>Narrator: "Hurry now... a moment more and she'll die" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-006.mp4", voiceSequence: null, choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "gss_02_007" } } } ] }, { id: "gss_02_007", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: ` <p>Narrator: "Hurry now... a moment more and she'll die" </p> <p>You: "*Hesitantly* Hey! You... You..." </p> <p>Narrator: "What was that? Such a pathetic attempt, And now she's dead." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-007.mp4", voiceSequence: null, choices: [ { text: "- To Battle!", outcomes: { custom: { type: "combat", encounterId: "castle_gate_skirmish", onWinEvent: "gss_02_013", onLoseEvent: "gss_02_007", companionIds: ["robert_2", "theron_5"], includeRecruited: false, setState: { sabineAlive: false, sabineInThroneRoom: false } } } } ] }, { id: "gss_02_008", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>*You and your followers made your way down.*</p> <p>Narrator: "Hurry now... a moment more and she'll die" </p> <p>You: "Hey fuck face! Why don't you pick on someone your own size!" </p> <p>Narrator: "That certainly has caught his attention." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-008.mp4", voiceSequence: [null, null, null, 'gs_02_008'], voiceStartIndex: 3, choices: [ { text: "- To Battle!", outcomes: { condition: () => State?.variables?.bellaRecruited === true, custom: { type: "combat", encounterId: "castle_gate_skirmish", onWinEvent: "gss_02_010_no_narrator", onLoseEvent: "gss_02_007", companionIds: ["robert_2", "theron_5", "bella_1"], includeRecruited: false, setState: { sabineAlive: true, sabineInThroneRoom: true } } } }, { text: "- To Battle!", outcomes: { condition: () => !State?.variables?.bellaRecruited, custom: { type: "combat", encounterId: "castle_gate_skirmish", onWinEvent: "gss_02_010_no_narrator", onLoseEvent: "gss_02_007", companionIds: ["robert_2", "theron_5"], includeRecruited: false, setState: { sabineAlive: true, sabineInThroneRoom: true } } } } ] }, { id: "gss_02_009", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: ` <p>Narrator: "Hurry now... a moment more and she'll die" </p> <p>Narrator: "How hard did you throw that rock? You've killed him almost instantly." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-009.mp4", voiceSequence: [null, 'gs_02_009'], voiceStartIndex: 1, choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "gss_02_010" } } } ] }, { id: "gss_02_010", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: ` <p>Narrator: "Hurry now... a moment more and she'll die" </p> <p>Narrator: "How hard did you throw that rock? You've killed him almost instantly." </p> <p>Sabine Orvenn: "My Lord! Thank you for saving me." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-010.mp4", voiceSequence: [null, null, 'gs_02_010'], voiceStartIndex: 2, choices: [ { text: "- Get yourself together and Follow me!", outcomes: { custom: { type: "connect", eventId: "gss_02_011a", recruitCharacter: "sabine_6", followCharacter: "sabine_6", setState: { sabineAlive: true, sabineInThroneRoom: true } }, affection: { target: "sabine_6" , delta: 52 } } }, { text: "- You're wounded, Get to safety!", outcomes: { custom: { type: "connect", eventId: "gss_02_011b" }, affection: { target: "sabine_6" , delta: 52 } } } ] }, { id: "gss_02_010_no_narrator", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: ` <p>Narrator: "Hurry now... a moment more and she'll die" </p> <p>Sabine Orvenn: "My Lord! Thank you for saving me." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-010.mp4", voiceSequence: [null, 'gs_02_010'], voiceStartIndex: 1, choices: [ { text: "- Get yourself together and Follow me!", outcomes: { custom: { type: "connect", eventId: "gss_02_011a", recruitCharacter: "sabine_6", followCharacter: "sabine_6", setState: { sabineAlive: true, sabineInThroneRoom: true } }, affection: { target: "sabine_6" , delta: 52 } } }, { text: "- You're wounded, Get to safety!", outcomes: { custom: { type: "connect", eventId: "gss_02_011b" }, affection: { target: "sabine_6" , delta: 52 } } } ] }, { id: "gss_02_011a", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Narrator: "How hard did you throw that rock? You've killed him almost instantly." </p> <p>Sabine Orvenn: "My Lord! Thank you for saving me." </p> <p>You: "Get yourself together! I need you to follow me, Let's push these bastards back." </p> <p>Sabine Orvenn: "As you command my Lord." </p> <p>*Sabine Orvenn is following you.*</p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-011.mp4", voiceSequence: [null, null, null, 'gs_02_011'], voiceStartIndex: 3, choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "gss_02_013", setState: { sabineAlive: true, sabineInThroneRoom: true } } } } ] }, { id: "gss_02_011b", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Narrator: "How hard did you throw that rock? You've killed him almost instantly." </p> <p>Sabine Orvenn: "My Lord! Thank you for saving me." </p> <p>You: "Don't thank me yet, You're wounded. Get yourself to safety!" </p> <p>Sabine Orvenn: "As you command my Lord." </p> <p>*Sabine Orvenn has returned into the castle.*</p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-011.mp4", voiceSequence: [null, null, null, 'gs_02_011'], voiceStartIndex: 3, choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "gss_02_013", setState: { sabineAlive: true, sabineInThroneRoom: true } } } } ] }, { id: "gss_02_013", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: ` <p>*You continued on to reach your father.*</p> <p>You: "Quickly! to the aid of my Father!" </p> <p>*As you reach closer, A Vargun Axe man has struck a blow to your father's abdomen.*</p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-013.mp4", voiceSequence: null, choices: [ { text: "- Noooo!", outcomes: { custom: { type: "connect", eventId: "gss_02_014" } } } ] }, { id: "gss_02_014", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>*As you reach closer a Vargun Axe man has struck a blow to your father's abdomen.*</p> <p>*Robert , Your Father has been critically wounded in battle.*</p> <p>*You've caught the attention of the group of men that killed your father.*</p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-014.mp4", voiceSequence: null, choices: [ { text: "- Attack!", outcomes: { condition: () => State?.variables?.sabineAlive === true && State?.variables?.bellaRecruited === true, custom: { type: "combat", encounterId: "aid_father_battle", onWinEvent: "gss_02_015", onLoseEvent: "gss_02_013", companionIds: ["robert_2", "theron_5", "sabine_6", "bella_1"], includeRecruited: false } } }, { text: "- Attack!", outcomes: { condition: () => State?.variables?.sabineAlive === true && !State?.variables?.bellaRecruited, custom: { type: "combat", encounterId: "aid_father_battle", onWinEvent: "gss_02_015", onLoseEvent: "gss_02_013", companionIds: ["robert_2", "theron_5", "sabine_6"], includeRecruited: false } } }, { text: "- Attack!", outcomes: { condition: () => !State?.variables?.sabineAlive && State?.variables?.bellaRecruited === true, custom: { type: "combat", encounterId: "aid_father_battle", onWinEvent: "gss_02_015", onLoseEvent: "gss_02_013", companionIds: ["robert_2", "theron_5", "bella_1"], includeRecruited: false } } }, { text: "- Attack!", outcomes: { condition: () => !State?.variables?.sabineAlive && !State?.variables?.bellaRecruited, custom: { type: "combat", encounterId: "aid_father_battle", onWinEvent: "gss_02_015", onLoseEvent: "gss_02_013", companionIds: ["robert_2", "theron_5"], includeRecruited: false } } } ] }, { id: "gss_02_015", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>Roderic Kain: "We did it my Lord! They're all Dead." </p> <p>Theron Fenric: "We should help the wounded." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-015.mp4", voiceSequence: ['gs_02_015_sp2', 'gs_02_015_sp5'], choices: [ { text: "- Check up on Father.", outcomes: { custom: { type: "connect", eventId: "gss_02_016", completeObjective: { questId: "quest_aid_father", index: 0 }, completeQuest: "quest_aid_father" } } } ] }, { id: "gss_02_016", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>You: "Father! Are you alright?" </p> <p>Robert : "......" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-016.mp4", voiceSequence: null, choices: [ { text: "- Pick him up.", outcomes: { custom: { type: "connect", eventId: "gss_02_017" } } } ] }, { id: "gss_02_017", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>You: "Father! Are you alright?" </p> <p>Robert : "......" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-017.mp4", voiceSequence: null, choices: [ { text: "- Shout louder.", outcomes: { custom: { type: "connect", eventId: "gss_02_017a" } } }, { text: "- Slap him!", outcomes: { custom: { type: "connect", eventId: "gss_02_017b" } } } ] }, { id: "gss_02_017a", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>You: "Father! Are you alright?" </p> <p>Robert : "......" </p> <p>You: "FATHER! WAKE UP!" </p> <p>Theron Fenric: "I'm sorry my Lord, He's gone." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-017A.mp4", voiceSequence: [null, null, null, 'gs_02_017ab'], voiceStartIndex: 3, choices: [ { text: "- Take care of him, I need to talk to Mother.", outcomes: { custom: { type: "connect", eventId: "gss_02_018" } } } ] }, { id: "gss_02_017b", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>You: "Father! Are you alright?" </p> <p>Robert : "......" </p> <p>*You've slapped your father's lifeless face.*</p> <p>Theron Fenric: "I'm sorry my Lord, He's gone." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-017B.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-017B-Idle.mp4", voiceSequence: [null, null, null, 'gs_02_017ab'], voiceStartIndex: 3, choices: [ { text: "- Take care of him, I need to talk to Mother.", outcomes: { custom: { type: "connect", eventId: "gss_02_018" } } } ] }, { id: "gss_02_018", category: "story", storyArc: "guard_awakening", message: "Vivid Dream", description: `<p>You: "Father! Are you alright?" </p> <p>Robert : "......" </p> <p>You: "Father! Wake up!" </p> <p>Theron Fenric: "I'm sorry my Lord, He's gone." </p> <p>You: "Take care of him, I need to talk to Mother." </p> <p>Theron Fenric: "It will be done my Lord." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-02-018.mp4", voiceSequence: [null, null, null, null, null, 'gs_02_018'], voiceStartIndex: 5, choices: [ { text: "- Leave.", outcomes: { custom: { type: "enterBuildingInterior", buildingInteriorId: "castle", giveQuest: "quest_aftermath" } } } ] }, { id: "gss_05_001", category: "story", storyArc: "throne_room", message: "Throne Room", description: ` <p>Eleanor : "My Son! Are you alright? Did you find Evelyn?" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-001.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-Idle.mp4", voiceSequence: ['gs_05_001'], choices: [ { text: "- Tell her about Evelyn.", outcomes: { custom: { type: "connect", eventId: "gss_05_002", completeObjective: { questId: "quest_find_family", index: 0 }, completeQuest: "quest_find_family" } } }, { text: "- Leave.", outcomes: { custom: { type: "connect", eventId: "gss_05_012_no_evelyn" } } } ] }, { id: "gss_05_002", category: "story", storyArc: "throne_room", message: "Throne Room", description: ` <p>Eleanor : "My Son! Are you alright? Did you find Evelyn?" </p> <p>You: "No Mother. I didn't see Evelyn anywhere outside the castle but there's something else you should know." </p> <p>Eleanor : "Well? Out with it!" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-002.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-Idle.mp4", voiceSequence: [null, null, 'gs_05_002'], voiceStartIndex: 2, choices: [ { text: "- Tell her about Father.", outcomes: { custom: { type: "connect", eventId: "gss_05_003" } } }, ] }, { id: "gss_05_003", category: "story", storyArc: "throne_room", message: "Throne Room", description: ` <p>Eleanor : "My Son! Are you alright? Did you find Evelyn?" </p> <p>You: "I didn't see Evelyn anywhere outside the castle but there's something else you should know." </p> <p>Eleanor : "Well? Out with it!" </p> <p>You: "Mother.. I don't know how to say it. But Father is dead." </p> <p>Eleanor : "What?! Robert is dead?... No it can't be." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-003.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-Idle.mp4", voiceSequence: [null, null, null, null, 'gs_05_003'], voiceStartIndex: 4, choices: [ { text: "- I'm sorry. I couldn't reach him in time.", outcomes: { custom: { type: "connect", eventId: "gss_05_004" } } } ] }, { id: "gss_05_004", category: "story", storyArc: "throne_room", message: "Throne Room", description: `<p>Eleanor : "What?! Robert is dead?... No it can't be. </p> <p>You: "I tried.. I couldn't reach him in time, It's my fault his dead." </p> <p>Eleanor : "No. Do not carry that weight. Your father made his choice to stand and protect our people. That was who he was. His death is not your doing." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-004.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-Idle.mp4", voiceSequence: [null, null, 'gs_05_004'], voiceStartIndex: 2, choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "gss_05_005", completeQuest: "quest_aftermath" } } } ] }, { id: "gss_05_005", category: "story", storyArc: "throne_room", message: "Throne Room", description: ` <p>Eleanor : "He would not want you drowning in guilt. He would want you standing tall. You are his son... and now, you are the Lord of these lands. Everything he protected, everything he built, now falls to you." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-005.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-Idle.mp4", voiceSequence: ['gs_05_005'], choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "gss_05_006" } } } ] }, { id: "gss_05_006", category: "story", storyArc: "throne_room", message: "Throne Room", description: `<p>Eleanor : "Our home is wounded. The villages, the farms, the people... they all need direction. They need hope. You must rebuild what was lost. Not because you are ready, but because it's also your birthright." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-006.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-Idle.mp4", voiceSequence: ['gs_05_006'], choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "gss_05_007" } } } ] }, { id: "gss_05_007", category: "story", storyArc: "throne_room", message: "Throne Room", description: ` <p>Eleanor : "I know this is heavy. But you will not face this alone. I am still here. And together, we will put this place back on its feet." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-007.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-Idle.mp4", voiceSequence: ['gs_05_007'], choices: [ { text: "- What about Evelyn?", outcomes: { custom: { type: "connect", eventId: "gss_05_008" } } } ] }, { id: "gss_05_008", category: "story", storyArc: "throne_room", message: "Throne Room", description: ` <p>Eleanor : "I know this is heavy. But you will not face this alone. I am still here. And together, we will put this place back on its feet." </p> <p>You: "What about Evelyn?" </p> <p>Eleanor : "We will find her. She wouldn't just vanish. Someone must have seen something... a guard, a villager, anyone who survived. Start there. Ask questions. Follow whatever trail you can." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-008.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-Idle.mp4", voiceSequence: [null, null, 'gs_05_008'], voiceStartIndex: 2, choices: [ { text: "- Continue", outcomes: { custom: { type: "connect", eventId: "gss_05_009", giveQuest: "quest_dear_sister" } } } ] }, { id: "gss_05_009", category: "story", storyArc: "throne_room", message: "Throne Room", description: `<p>Eleanor : "We will find her. She wouldn't just vanish. Someone must have seen something... a guard, a villager, anyone who survived. Start there. Ask questions. Follow whatever trail you can." </p> <p>Eleanor : "But first... this castle, our lands, they need you. The people are scared, the walls are broken, and our soldiers are scattered. Help them, rebuild what was lost, and gather your strength. Once things are steadier, you will have the means to bring Evelyn home safely." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-009.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-Idle.mp4", voiceSequence: [null, 'gs_05_009'], voiceStartIndex: 1, choices: [ { text: "- Where do I start?", outcomes: { custom: { type: "connect", eventId: "gss_05_010" } } } ] }, { id: "gss_05_010", category: "story", storyArc: "throne_room", message: "Throne Room", description: `<p>Eleanor : "But first... this castle, our lands, they need you. The people are scared, the walls are broken, and our soldiers are scattered. Help them, rebuild what was lost, and gather your strength. Once things are steadier, you will have the means to bring Evelyn home safely." </p> <p>You: "Alright... Where do I start?" </p> <p>Eleanor : "Start by roaming our lands and repair what's been destroyed, gather resources from those establishments." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-010.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-Idle.mp4", voiceSequence: [null, null, 'gs_05_010'], voiceStartIndex: 2, choices: [ { text: "- Understood.", outcomes: { custom: { type: "connect", eventId: "gss_05_011" } } } ] }, { id: "gss_05_011", category: "story", storyArc: "throne_room", message: "Throne Room", description: ` <p>Eleanor : "But first... this castle, our lands, they need you. The people are scared, the walls are broken, and our soldiers are scattered. Help them, rebuild what was lost, and gather your strength. Once things are steadier, you will have the means to bring Evelyn home safely." </p> <p>You: "Alright... Where do I start?" </p> <p>Eleanor : "Start by roaming our lands and repair what's been destroyed, gather resources from those establishments." </p> <p>You: "Thank you, Mother. I shall take my leave now." </p> <p>Eleanor : "Remember my son, You are Lord of these Lands now. May Solvarion bless you. Farewell." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-011.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-Idle.mp4", voiceSequence: [null, null, null, null, 'gs_05_011'], voiceStartIndex: 4, choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding", giveQuest: "quest_your_lands", setState: { talkedToElanorAftermath: true } } } } ] }, { id: "gss_05_012_no_evelyn", category: "story", storyArc: "throne_room", message: "Throne Room", description: ` <p>Eleanor : "My Son! Are you alright? Where's your father? Did you find Evelyn?" </p> <p>Eleanor : "Going somewhere? Oh no you don't. You march right back here, young man. You think you can disappear on me after a battle without a single word? Not happening. Now speak." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-012.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-05-Idle.mp4", voiceSequence: [null, 'gs_05_012'], voiceStartIndex: 1, choices: [ { text: "- Tell her about Evelyn", outcomes: { custom: { type: "connect", eventId: "gss_05_002" } } } ] }, { id: "gss_06_001", category: "story", storyArc: "sabine_recruitment", message: "Throne Room", description: ` <p>Sabine Orvenn: "My Lord! Are you hurt?" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-001.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-Idle.mp4", voiceSequence: ['gs_06_001'], choices: [ { text: "- What? No. I'm fine.", outcomes: { custom: { type: "connect", eventId: "gss_06_002" } } } ] }, { id: "gss_06_002", category: "story", storyArc: "sabine_recruitment", message: "Throne Room", description: ` <p>Sabine Orvenn: "My Lord! Are you hurt?" </p> <p>You: "What? No. I'm fine." </p> <p>Sabine Orvenn: "I just want to say, Thanks for saving me and I am forever in your debt." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-002.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-Idle.mp4", voiceSequence: [null, null, 'gs_06_002'], voiceStartIndex: 2, choices: [ { text: "- You owe me nothing.", outcomes: { custom: { type: "connect", eventId: "gss_06_003a" } } }, { text: "- I'm glad you're alright.", outcomes: { custom: { type: "connect", eventId: "gss_06_003b" } } } ] }, { id: "gss_06_003a", category: "story", storyArc: "sabine_recruitment", message: "Throne Room", description: ` <p>Sabine Orvenn: "My Lord! Are you hurt?" </p> <p>You: "What? No. I'm fine." </p> <p>Sabine Orvenn: "I just want to say, Thanks for saving me and I am forever in your debt." </p> <p>You: "You owe me nothing, Soldier. I wasn't about to let you die out there. I'm just relieved you made it back inside." </p> <p>Sabine Orvenn: "That's very kind of you my Lord." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-003.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-Idle.mp4", voiceSequence: [null, null, null, null, 'gs_06_003'], voiceStartIndex: 3, choices: [ { text: "- What is your name?", outcomes: { custom: { type: "connect", eventId: "gss_06_004" } } } ] }, { id: "gss_06_003b", category: "story", storyArc: "sabine_recruitment", message: "Throne Room", description: ` <p>Sabine Orvenn: "My Lord! Are you hurt?" </p> <p>You: "What? No. I'm fine." </p> <p>Sabine Orvenn: "I just want to say, Thanks for saving me and I am forever in your debt." </p> <p>You: "I'm glad you're alright, Soldier. Seeing you back on your feet is a relief." </p> <p>Sabine Orvenn: "That's very kind of you my Lord." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-003.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-Idle.mp4", voiceSequence: [null, null, null, null, null, null, 'gs_06_003'], voiceStartIndex: 7, choices: [ { text: "- What is your name?", outcomes: { custom: { type: "connect", eventId: "gss_06_004" } } } ] }, { id: "gss_06_004", category: "story", storyArc: "sabine_recruitment", message: "Throne Room", description: `<p>You: "What is your name? Soldier." </p> <p>Sabine Orvenn: "Allow me to introduce myself, My name's Sabine." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-004.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-Idle.mp4", voiceSequence: [null, 'gs_06_004'], voiceStartIndex: 1, choices: [ { text: "- Pleasure to meet you.", outcomes: { custom: { type: "connect", eventId: "gss_06_005" } } } ] }, { id: "gss_06_005", category: "story", storyArc: "sabine_recruitment", message: "Throne Room", description: `<p>You: "What is your name? Soldier." </p> <p>Sabine Orvenn: "Allow me to introduce myself, My name's Sabine." </p> <p>You: "It is a pleasure to meet you Sabine." </p> <p>Sabine Orvenn: "My Lord, if I may speak plainly. Let me serve under your command and repay the life you saved. I'm a capable swordswoman, and I don't falter under pressure. If you'll have me, I can serve wherever you need me most. Whether that means standing at your side in battle or managing responsibilities within the settlement, I'll ensure your people are protected and the work is done." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-005.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-Idle.mp4", voiceSequence: [null, null, null, 'gs_06_005'], voiceStartIndex: 3, choices: [ { text: "- Accept Offer.", outcomes: { custom: { type: "connect", eventId: "gss_06_006a", recruitCharacter: "sabine_6" } } }, { text: "- Reject Offer.", outcomes: { custom: { type: "connect", eventId: "gss_06_006b" } } } ] }, { id: "gss_06_006a", category: "story", storyArc: "sabine_recruitment", message: "Throne Room", description: `<p>You: "Very well, Sabine. Report to Roderic, I'll assign your duties once I'm able." </p> <p>Sabine Orvenn: "Right away! my Lord. Thank you. I won't disappoint you." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-006A.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-Idle.mp4", voiceSequence: [null, 'gs_06_006a'], voiceStartIndex: 1, choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding", giveQuest: "quest_aid_father", setState: { talkedToSabineThrone: true } } } } ] }, { id: "gss_06_006b", category: "story", storyArc: "sabine_recruitment", message: "Throne Room", description: `<p>You: "I appreciate your offer, Sabine, but I can't take you under my command at this time." </p> <p>Sabine Orvenn: "I understand, my Lord. I'll will still be here if you changed your mind." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-006B.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-06-Idle.mp4", voiceSequence: [null, 'gs_06_006b'], choices: [ { text: "- Leave.", outcomes: { custom: {type: "returnToBuilding", setState: { talkedToSabineThrone: true } } } } ] }, { id: "gss_03_001", category: "story", storyArc: "finding_family", message: "Throne Room", description: ` <p>You: "Mother? Evelyn? are you here?" </p> <p>Eleanor: "My Son! I'm here!" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-001.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-Idle.mp4", voiceSequence: [null, 'gs_03_001'], voiceStartIndex: 1, choices: [ { text: "- Where's my sister?", outcomes: { custom: { type: "connect", eventId: "gss_03_002", setState: { talkedToElanorFindFamily: true }, completeQuest: "quest_find_family"} } } ] }, { id: "gss_03_002", category: "story", storyArc: "finding_family", message: "Throne Room", description: `<p>You: "Mother? Evelyn? are you here?" </p> <p>Eleanor: "My Son! I'm here!" </p> <p>You: "Where's Evelyn?" </p> <p>Eleanor: "I couldn't find your Evelyn, she isn't in her room." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-002.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-Idle.mp4", voiceSequence: [null, null, null, 'gs_03_002'], voiceStartIndex: 3, choices: [ { text: "- Where could she be?", outcomes: { custom: { type: "connect", eventId: "gss_03_003" } } } ] }, { id: "gss_03_003", category: "story", storyArc: "finding_family", message: "Throne Room", description: `<p>You: "Mother? Evelyn? are you here?" </p> <p>Eleanor: "My Son! I'm here!" </p> <p>You: "Where's Evelyn?" </p> <p>Eleanor: "I couldn't find your Evelyn, she isn't in her room." </p> <p>You: "Where could she be?" </p> <p>Eleanor: "She must have sneaked out the castle again!" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-003.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-Idle.mp4", voiceSequence: [null, null, null, null, null, 'gs_03_003'], voiceStartIndex: 5, choices: [ { text: "- What now?", outcomes: { custom: { type: "connect", eventId: "gss_03_004" } } } ] }, { id: "gss_03_004", category: "story", storyArc: "finding_family", message: "Throne Room", description: `<p>You: "What now?" </p> <p>Eleanor: "I swear that girl is going to have it from me!" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-004.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-Idle.mp4", voiceSequence: [null, 'gs_03_004'], voiceStartIndex: 1, choices: [ { text: "- Mother I have to help Father.", outcomes: { custom: { type: "connect", eventId: "gss_03_005" } } } ] }, { id: "gss_03_005", category: "story", storyArc: "finding_family", message: "Throne Room", description: `<p>You: "Mother! It's alright, there's more pressing matter at hand. I should be out there helping father! we're all dead if we just sit here and do nothing!" </p> <p>Eleanor: "My dear son, listen to me. I know your heart is brave, just like your father's, but running out there without thinking will not save him. I can feel the danger in the air. The shouting, the smoke... it terrifies me. I've already lost track of your sister. I cannot bear the thought of losing you too." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-005.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-Idle.mp4", voiceSequence: [null, 'gs_03_005'], voiceStartIndex: 1, choices: [ { text: "- He needs me.", outcomes: { custom: { type: "connect", eventId: "gss_03_006" } } } ] }, { id: "gss_03_006", category: "story", storyArc: "finding_family", message: "Throne Room", description: `<p>Eleanor: "My dear son, listen to me. I know your heart is brave, just like your father's, but running out there without thinking will not save him. I can feel the danger in the air. The shouting, the smoke... it terrifies me. I've already lost track of your sister. I cannot bear the thought of losing you too." </p> <p>You: "Mother... I can't stand by while he fights alone. He needs me." </p> <p>Eleanor: "I know. And you're right. Your father needs all the help he can get. But you must promise me, Do not rush into danger blindly. Move carefully, and choose your moments wisely. I cannot lose you. Do you understand?" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-006.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-Idle.mp4", voiceSequence: [null, null, 'gs_03_006'], voiceStartIndex: 2, choices: [ { text: "- I understand.", outcomes: { custom: { type: "connect", eventId: "gss_03_007" } } } ] }, { id: "gss_03_007", category: "story", storyArc: "finding_family", message: "Throne Room", description: ` <p>You: "Mother... I can't stand by while he fights alone. He needs me." </p> <p>Eleanor: "I know. And you're right that he needs all the help he can get. But you must promise me you'll be careful. Do not rush into danger blindly. Stay low, keep to the shadows if you must, and come back to me. Come back alive. I cannot lose you. Do you understand?" </p> <p>You: "I understand. Besides I have Rodrick and Theron with me. I'll be fine." </p> <p>Eleanor: "Then go. Help your father. But let me see you return through that door when it's over. That is the only thing I ask." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-007.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-Idle.mp4", voiceSequence: [null, null, null, 'gs_03_007'], voiceStartIndex: 3, choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding", giveQuest: "quest_aid_father" } } } ] }, { id: "gss_04_001", category: "story", storyArc: "cellar_bella", message: "Cellar", description: `<p>You: "Mother! Evelyn! are you here?" </p> <p>Unknown Voice: "My Lord?" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-001.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-Idle.mp4", voiceSequence: [null, 'gs_04_001'], choices: [ { text: "- Who's there?", outcomes: { custom: { type: "connect", eventId: "gss_04_002", setState: { metBellaCellar: true } } } } ] }, { id: "gss_04_002", category: "story", storyArc: "cellar_bella", message: "Cellar", description: `<p>You: "Mother! Evelyn! are you here?" </p> <p>Unknown Voice: "My Lord?" </p> <p>You: "Who goes there? Show yourself!" </p> <p>Bella: "Forgive me, My Lord. It's me. Bella! I am a maid in your household!" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-002.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-Idle.mp4", voiceSequence: [null, null, null, 'gs_04_002'], voiceStartIndex: 3, choices: [ { text: "- Have you seen my Mother or Sister?", outcomes: { custom: { type: "connect", eventId: "gss_04_003" } } } ] }, { id: "gss_04_003", category: "story", storyArc: "cellar_bella", message: "Cellar", description: `<p>You: "Mother! Evelyn! are you here?" </p> <p>Unknown Voice: "My Lord?" </p> <p>You: "Who goes there? Show yourself!" </p> <p>Bella: "Forgive me, My Lord! It's me, Bella! I am a maid in your household!" </p> <p>You: "Have you seen my Mother or Sister?" </p> <p>Bella: "I've not seen Lady Evelyn, but the Lady Eleanor was just here looking for her as well. I believe Lady Eleanor is heading towards the throne room." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-003.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-Idle.mp4", voiceSequence: [null, null, null, null, null, 'gs_04_003'], voiceStartIndex: 5, choices: [ { text: "- Alright. Stay here and protect yourself.", outcomes: { custom: { type: "connect", eventId: "gss_04_006" } } }, { text: "- Come with me, I need all the help I can get.", outcomes: { custom: { type: "connect", eventId: "gss_04_004" } } } ] }, { id: "gss_04_004", category: "story", storyArc: "cellar_bella", message: "Cellar", description: `<p>Bella: "I've not seen Lady Evelyn, but the Lady Eleanor was just here looking for Lady Evelyn as well. I believe Lady Eleanor is heading towards the throne room." </p> <p>You: "Alright. Come with me, I need all the help I can get." </p> <p>Bella: "My Lord?" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-004.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-Idle.mp4", voiceSequence: [null, null, 'gs_04_004'], voiceStartIndex: 2, choices: [ { text: "- Continue", outcomes: { custom: { type: "connect", eventId: "gss_04_005" } } } ] }, { id: "gss_04_005", category: "story", storyArc: "cellar_bella", message: "Cellar", description: `<p>You: "We need to fight Bella, Surely you can't just hide in here, waiting to die or worst." </p> <p>Bella: "But.... I.... don't know how to fight! I've never yielded a sword before..." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-005.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-Idle.mp4", voiceSequence: [null, 'gs_04_005'], voiceStartIndex: 1, choices: [ { text: "- You'll be fine. (Convince Bella) (Dexterity Roll)", diceRoll: { required: true, target: 4, context: "dexterity" }, outcomes: { custom: { type: "connect", eventId: "gss_04_007a", recruitCharacter: "bella_1", followCharacter: "bella_1", setState: { bellaRecruited: true } } }, failureOutcomes: { custom: { type: "connect", eventId: "gss_04_007b" } } }, { text: "- You're right, Stay here and keep your head down.", outcomes: { custom: { type: "connect", eventId: "gss_04_006" } } } ] }, { id: "gss_04_006", category: "story", storyArc: "cellar_bella", message: "Cellar", description: `<p>Bella: "I've not seen Lady Evelyn, but the Lady Eleanor was just here looking for her as well. I believe Lady Eleanor is heading towards the throne room." </p> <p>You: "Alright. Stay here and find something sharp to protect yourself. Kill anyone that comes through those doors that you don't recognize. This should be over soon." </p> <p>Bella: "Yes, My Lord. Please be safe." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-006.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-Idle.mp4", voiceSequence: [null, null, 'gs_04_006'], voiceStartIndex: 2, choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "gss_04_007a", category: "story", storyArc: "cellar_bella", message: "Cellar", description: ` <p>You: "It's easy just stick em with the pointy end." </p> <p>Bella: "Alright.. As you command my Lord." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-007A.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-Idle.mp4", voiceSequence: [null, null, 'gs_04_007a', null], choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "gss_04_007b", category: "story", storyArc: "cellar_bella", message: "Cellar", description: `<p>You: "You're right, Stay here and keep your head down. This should be over soon." </p> <p>Bella: "Yes, My Lord. Please be safe." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-007B.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-Idle.mp4", voiceSequence: [null, 'gs_04_006'], voiceStartIndex: 1, choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "gss_04_006b", category: "story", storyArc: "cellar_bella", message: "Cellar", description: `<p>You: "You're right, Stay here and keep your head down. This should be over soon." </p> <p>Bella: "Yes, My Lord. Please be safe." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-006.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-Idle.mp4", voiceSequence: [null, 'gs_04_006'], choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "gss_04_007b_failed", category: "story", storyArc: "cellar_bella", message: "Cellar", description: `<p>*Roll Failed*</p> <p>You: "It's easy just stick em with the pointy end." </p> <p>Bella: "I'm scared my Lord, I'm sorry but I just can't." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-007B.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-Idle.mp4", voiceSequence: [null, null, 'gs_04_007b', null], choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "cellar_root", category: "story", storyArc: "castle_cellar", message: "Cellar", description: `<p>The cellar is dim and cold. Dust hangs in the air.</p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-001.mp4", voiceSequence: null, choices: [ { text: "- Look around the room.", outcomes: { custom: { type: "routeCellarScan" } } }, { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "cellar_empty", category: "story", storyArc: "castle_cellar", message: "Cellar", description: `<p>The cellar is empty for now.</p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-04-001.mp4", voiceSequence: null, choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "throne_room_root", category: "story", storyArc: "castle_throne_room", message: "Throne Room", description: `<p>You step into the throne room. The air is heavy with incense and power.</p> <p>What do you do?</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Throne_Room.png", choices: [ { text: "- Look around the room.", outcomes: { custom: { type: "routeThroneRoomScan" } } }, { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "throne_room_search", category: "story", storyArc: "castle_throne_room", message: "Throne Room", description: `<p>The room seems empty.</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Throne_Room.png", choices: [ { text: "- Leave.", outcomes: { custom: { type: "connect", eventId: "throne_room_empty" } } } ] }, { id: "throne_room_roster", category: "story", storyArc: "castle_throne_room", message: "Throne Room", description: `<p>You looked at the room.</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Throne_Room.png", choices: [ { text: "- Speak with Eleanor.", outcomes: { custom: { type: "connect", eventId: "gss_05_001" }, condition: () => { const questState = State && State.variables && State.variables.questTracking; const activeQuests = Array.isArray(questState?.active) ? questState.active.map(q => q && q.id) : []; return activeQuests.includes('quest_aftermath') && !State?.variables?.talkedToElanorAftermath; } } }, { text: "- Speak with Eleanor.", outcomes: { custom: { type: "connect", eventId: "gss_03_001" }, condition: () => { const questState = State && State.variables && State.variables.questTracking; const activeQuests = Array.isArray(questState?.active) ? questState.active.map(q => q && q.id) : []; return activeQuests.includes('quest_find_family') && !State?.variables?.talkedToElanorFindFamily; } } }, { text: "- Approach Sabine.", outcomes: { custom: { type: "connect", eventId: "gss_06_001" }, condition: () => { return State?.variables?.sabineAlive && State?.variables?.sabineInThroneRoom && !State?.variables?.talkedToSabineThrone; } } }, { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "throne_room_empty", category: "story", storyArc: "castle_throne_room", message: "Throne Room", description: `<p>The throne room is empty. Only the echo of your footsteps answers.</p>`, mediaType: "video", mediaPath: "Bin/Contents/Scenes/Human/Start/GS-03-005.mp4", voiceSequence: null, choices: [ { text: "- Test your luck at the throne.", diceRoll: { required: true, target: 4, context: "luck" }, outcomes: { custom: { type: "connect", eventId: "throne_room_armor_success", giveItem: { itemId: "heavy_plate_armor", quantity: 1 }, setState: { throneRoomArmorFound: true } }, condition: () => !State?.variables?.throneRoomArmorFound }, failureOutcomes: { custom: { type: "connect", eventId: "throne_room_armor_fail", setState: { throneRoomArmorFound: true } } } }, { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "throne_room_armor_success", category: "story", storyArc: "castle_throne_room", message: "Throne Room", description: `<p>You spot a hidden cache beneath the dais. Inside rests a suit of heavy armor.</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Throne_Room.png", choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "throne_room_armor_fail", category: "story", storyArc: "castle_throne_room", message: "Throne Room", description: `<p>You search the shadows, but find nothing of value.</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Throne_Room.png", choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "dining_hall_night_root", category: "story", storyArc: "castle_dining_hall_night", message: "Dining Hall", description: `<p>You step into the dining hall. The room is quiet in the night.</p> <p>What do you do?</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Dining_Hall_Night.png", choices: [ { text: "- Look around the room.", outcomes: { custom: { type: "connect", eventId: "dining_hall_night_empty" } } }, { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "dining_hall_night_empty", category: "story", storyArc: "castle_dining_hall_night", message: "Dining Hall", description: `<p>The room seems empty.</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Dining_Hall_Night.png", choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "library_night_root", category: "story", storyArc: "castle_library_night", message: "Library", description: `<p>You step into the library. The room is quiet in the night.</p> <p>What do you do?</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Library_Night.png", choices: [ { text: "- Look around the room.", outcomes: { custom: { type: "connect", eventId: "library_night_empty" } } }, { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "library_night_empty", category: "story", storyArc: "castle_library_night", message: "Library", description: `<p>The room seems empty.</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Library_Night.png", choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "noble_quarters_a_night_root", category: "story", storyArc: "castle_noble_quarters_a_night", message: "Noble Quarters A", description: `<p>You step into the noble quarters. The room is quiet in the night.</p> <p>What do you do?</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Noble_Quarters_A_Night.png", choices: [ { text: "- Look around the room.", outcomes: { custom: { type: "connect", eventId: "noble_quarters_a_night_empty" } } }, { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "noble_quarters_a_night_empty", category: "story", storyArc: "castle_noble_quarters_a_night", message: "Noble Quarters A", description: `<p>The room seems empty.</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Noble_Quarters_A_Night.png", choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "noble_quarters_b_night_root", category: "story", storyArc: "castle_noble_quarters_b_night", message: "Noble Quarters B", description: `<p>You step into the noble quarters. The room is quiet in the night.</p> <p>What do you do?</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Noble_Quarters_B_Night.png", choices: [ { text: "- Look around the room.", outcomes: { custom: { type: "connect", eventId: "noble_quarters_b_night_empty" } } }, { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "noble_quarters_b_night_empty", category: "story", storyArc: "castle_noble_quarters_b_night", message: "Noble Quarters B", description: `<p>The room seems empty.</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Noble_Quarters_B_Night.png", choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "player_quarters_night_root", category: "story", storyArc: "castle_player_quarters_night", message: "Player Quarters", description: `<p>You step into the player quarters. The room is quiet in the night.</p> <p>What do you do?</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Player_Quarters_Night.png", choices: [ { text: "- Look around the room.", outcomes: { custom: { type: "connect", eventId: "player_quarters_night_empty" } } }, { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "player_quarters_night_empty", category: "story", storyArc: "castle_player_quarters_night", message: "Player Quarters", description: `<p>The room seems empty.</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Player_Quarters_Night.png", choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "throne_room_night_root", category: "story", storyArc: "castle_throne_room_night", message: "Throne Room", description: `<p>You step into the throne room. The room is quiet in the night.</p> <p>What do you do?</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Throne_Room_Night.png", choices: [ { text: "- Look around the room.", outcomes: { custom: { type: "connect", eventId: "throne_room_night_empty" } } }, { text: "- Test your luck.", diceRoll: { required: true, target: 4, context: "luck" }, outcomes: { custom: { type: "connect", eventId: "throne_room_night_loot_success", giveItem: { id: "heavy_plate_armor", quantity: 1 } } }, failureOutcomes: { custom: { type: "connect", eventId: "throne_room_night_loot_fail" } } }, { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "throne_room_night_empty", category: "story", storyArc: "castle_throne_room_night", message: "Throne Room", description: `<p>The room seems empty.</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Throne_Room_Night.png", choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "throne_room_night_loot_success", category: "story", storyArc: "castle_throne_room_night", message: "Throne Room", description: `<p>You lift a loose floorboard and discover a suit of heavy armor, wrapped in old cloth.</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Throne_Room_Night.png", choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "throne_room_night_loot_fail", category: "story", storyArc: "castle_throne_room_night", message: "Throne Room", description: `<p>You search for a time, but find nothing of value.</p>`, mediaType: "image", mediaPath: "Bin/Contents/Locations/Player_Settlement/Scenes/Castle/Throne_Room_Night.png", choices: [ { text: "- Leave.", outcomes: { custom: { type: "returnToBuilding" } } } ] }, { id: "inter15_r3_01_01a", category: "story", storyArc: "sabine_throne_room_inter15", repeatEventId: "inter15_r3_01_01b", message: "Throne Room", description: `<p>Sabine Orvenn: "How may I be of service, my Lord?" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_01.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_00_Idle.mp4", voiceSequence: ['Inter15_R3_01_01'], choices: [ { text: "- I saved your life. Reward me.", outcomes: { custom: { type: "connect", eventId: "inter15_r3_01_02a" } } }, { text: "- Leave.", outcomes: { custom: { type: "returnToCompanionInventory" } } } ] }, { id: "inter15_r3_01_01b", category: "story", storyArc: "sabine_throne_room_inter15", message: "Throne Room", description: `<p>Sabine Orvenn: "How may I be of service, my Lord?" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_01.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_00_Idle.mp4", voiceSequence: ['Inter15_R3_01_01'], choices: [ { text: "- I want another handjob.", outcomes: { custom: { type: "connect", eventId: "inter15_r3_01_02b" } } }, { text: "- Leave.", outcomes: { custom: { type: "returnToCompanionInventory" } } } ] }, { id: "inter15_r3_01_02a", category: "story", storyArc: "sabine_throne_room_inter15", message: "Servant Room", description: `<p>Sabine Orvenn: "How may I be of service, my Lord?" </p> <p>You: "Sabine, I saved your life during the attack. Now that I'm feeling quite... restless, I would like you to repay me with your body." </p> <p>Sabine Orvenn: "My Lord, you saved my life. I am forever in your debt, and I... I am willing. But I must confess, I have never done this before." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_02.mp4", voiceSequence: ['Inter15_R3_01_01', null, 'Inter15_R3_01_02'], idleMediaType: "video", idleMediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_00_Idle.mp4", voiceStartIndex: 2, choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "inter15_r3_01_03" } } } ] }, { id: "inter15_r3_01_02b", category: "story", storyArc: "sabine_throne_room_inter15", message: "Throne Room", description: `<p>Sabine Orvenn: "How may I be of service, my Lord?" </p> <p>You: "Sabine, I am in need of your services again. I want you to pleasure me." </p> <p>Sabine Orvenn: "Oh! Um.. Of course my lord." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_02B.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_00_Idle.mp4", voiceSequence: ['Inter15_R3_01_01', null, 'Inter15_R3_01_02B'], voiceStartIndex: 2, choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "Inter15_R3_01_05b" } } } ] }, { id: "inter15_r3_01_03", category: "story", storyArc: "sabine_throne_room_inter15", message: "Throne Room", description: ` <p>Sabine Orvenn: "My Lord, I wish to keep my virginity intact. But I am willing to show my gratitude in another way. Perhaps I could pleasure you with my hands instead? That way I could still repay you for saving my life." </p> <p>Narrator: "Well, that's an unexpected compromise. It seems she's not giving up her virtue just yet. What will you do?" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_03.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_00_Idle.mp4", voiceSequence: ['Inter15_R3_01_03', 'Inter15_R3_01_03NA'], choices: [ { text: "- Accept Handjob.", outcomes: { custom: { type: "connect", eventId: "inter15_r3_01_04a" } } }, { text: "- Leave.", outcomes: { custom: { type: "returnToCompanionInventory" } } } ] }, { id: "inter15_r3_01_04a", category: "story", storyArc: "sabine_throne_room_inter15", message: "Throne Room", description: `<p>Player: "Very well, Sabine. I accept your offer." </p> <p>Sabine Orvenn: "Thank you, my Lord. I will do my best to pleasure you." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_04.mp4", voiceSequence: [null, 'Inter15_R3_01_04'], voiceStartIndex: 1, idleMediaType: "video", idleMediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_00_Idle.mp4", choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "Inter15_R3_01_05a" } } }, ] }, { id: "Inter15_R3_01_05b", category: "story", storyArc: "sabine_throne_room_inter15", message: "Throne Room", description: ` <p>Narrator: "That's cute." </p> <p>Sabine Orvenn: "I'm ready My Lord." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_05.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_05_Idle.mp4", voiceSequence: ['Inter15_R3_01_05NA', 'Inter15_R3_01_05'], choices: [ { text: "- Manual Controls.", outcomes: { custom: { type: "manualControls", position: "hj_clothed", eventId: "mc-sabine-hj-clothed" } } }, { text: "- Ask her to undress. (Charm Roll)", diceRoll: { required: true, target: 4, context: "charisma" }, outcomes: { custom: { type: "connect", eventId: "inter15_r3_01_06" } }, failureOutcomes: { custom: { type: "connect", eventId: "inter15_r3_01_08" } } }, { text: "- Skip.", outcomes: { custom: { type: "returnToCompanionInventory" } } } ] }, { id: "Inter15_R3_01_05a", category: "story", storyArc: "sabine_throne_room_inter15", message: "Throne Room", description: ` <p>Narrator: "That's cute." </p> <p>Sabine Orvenn: "I'm ready My Lord." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_05.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_05_Idle.mp4", voiceSequence: ['Inter15_R3_01_05NA', 'Inter15_R3_01_05'], choices: [ { text: "- Manual Controls.", outcomes: { custom: { type: "manualControls", position: "hj_clothed", eventId: "mc-sabine-hj-clothed" } } }, { text: "- Skip.", outcomes: { custom: { type: "returnToCompanionInventory" } } } ] }, { id: "inter15_r3_01_06", category: "story", storyArc: "sabine_throne_room_inter15", message: "Throne Room", description: ` <p>Sabine Orvenn: "I'm ready My Lord." </p> <p>Player: "Could you take off your clothes? I'd like to see your beautiful figure." </p> <p>Sabine Orvenn: "My Lord! I.. I suppose it's only fair, considering that I've also seen you naked before." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_06.mp4", voiceSequence: [null, null, 'Inter15_R3_01_06'], idleMediaType: "video", idleMediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_05_Idle.mp4", voiceStartIndex: 2, choices: [ { text: "- Continue.", outcomes: { custom: { type: "connect", eventId: "inter15_r3_01_07" } } } ] }, { id: "inter15_r3_01_07", category: "story", storyArc: "sabine_throne_room_inter15", message: "Throne Room", description: ` <p>Sabine Orvenn: "Does this please you? My Lord?" </p> <p>Narrator: "Oh my.. what a sight." </p>`, mediaType: "video", mediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_07.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_07_Idle.mp4", voiceSequence: ['Inter15_R3_01_07', 'Inter15_R3_01_07NA'], choices: [ { text: "- Manual Controls.", outcomes: { custom: { type: "manualControls", position: "hj_naked", eventId: "mc-sabine-hj-naked" } } }, { text: "- Skip.", outcomes: { custom: { type: "returnToCompanionInventory" } } } ] }, { id: "inter15_r3_01_08", category: "story", storyArc: "sabine_throne_room_inter15", message: "Throne Room", description: `<p>Sabine kneels and presents herself obediently at your groin.</p> <p>Sabine Orvenn: "I'm ready My Lord." </p> <p>Player: "Take off your clothes. I would like to see some breasts." </p> <p>Sabine Orvenn: "Sorry, My Lord. But I don't feel comfortable doing that right now." </p> <p>Narrator: "Perhaps take a more gentlemanly approach next time?" </p>`, mediaType: "video", mediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_08.mp4", idleMediaType: "video", idleMediaPath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/Interactions/Interaction_15/R_3/Inter15_R3_01_05_Idle.mp4", voiceSequence: [null, 'Inter15_R3_01_07', 'Inter15_R3_01_07NA'], voiceSequence: [null, null, null, null, 'Inter15_R3_01_08', 'Inter15_R3_01_08NA'], voiceStartIndex: 4, choices: [ { text: "- Manual Controls.", outcomes: { custom: { type: "manualControls", position: "hj_clothed", eventId: "mc-sabine-hj-clothed" } } }, { text: "- Skip.", outcomes: { custom: { type: "returnToCompanionInventory" } } } ] } ]>>
:: InteractiveVideoDefinitions [setup] <<set setup.interactiveVideoEvents = [ { id: "mc-bella-01", title: "Vivid Dream", position: "vaginal", controls: [ { id: "faster", label: "Faster", type: "advance", path: "bj" }, { id: "anal", label: "Anal", type: "advance", path: "anal" }, { id: "vaginal", label: "Vaginal", type: "advance", path: "vaginal" }, { id: "cum", label: "Cum", type: "cum" } ], progression: { bj: [1, 3, 4], anal: [1, 2, 3], vaginal: [1, 2, 3] }, videoBasePath: "Bin/Contents/Scenes/Human/Prologue/Sex/InterVag/", initialDialogue: "<p><strong>Bella:</strong><br>*Gasping* \"Please... Don't..\"</p>", speedDialogues: { 1: null, 2: "<p><strong>Bella:</strong><br>*Moaning* \"Ahh! Ahh!... Ahh!...\"</p>", 3: "<p><strong>Bella:</strong><br>*Moaning* \"Ahh! Please I can't take it anymore!\"</p>" }, climaxDialogues: { 0: "<p><strong>Bella:</strong><br>*Ahh* \"It hurts... Please, stop...\"</p>", 30: "<p><strong>Bella:</strong><br>*Gasping* \"I beg you my Lord... Stop...\"</p>", 60: "<p><strong>Bella:</strong><br>*Crying out* \"Please my Lord!...\"</p>", 90: "<p><strong>Bella:</strong><br>*Trembling* \"Something's coming out! I'm going to... please!\"</p>" }, cumDialogue: "<p><strong>Bella:</strong><br>*Breathing heavily* \"AHH!! STOP! I CAN'T CONTROL IT!\"</p>", postCumDialogue: "<p><strong>Bella:</strong><br>*Sobbing* \"Please.. Just Leave...\"</p>", onComplete: "gss_000_1" }, { id: "mc-bella-anal", title: "Vivid Dream", position: "anal", controls: [ { id: "faster", label: "Faster", type: "advance", path: "bj" }, { id: "anal", label: "Anal", type: "advance", path: "anal" }, { id: "vaginal", label: "Vaginal", type: "advance", path: "vaginal" }, { id: "cum", label: "Cum", type: "cum" } ], progression: { bj: [1, 3, 4], anal: [1, 2, 3], vaginal: [1, 2, 3] }, videoBasePath: "Bin/Contents/Scenes/Human/Prologue/Sex/InterAnal/", initialDialogue: "<p><strong>Bella:</strong><br>*Shock* \"WAIT!... NO! STOP! PLEASE!\"</p>", speedDialogues: { 1: null, 2: "<p><strong>Bella:</strong><br>*Crying out* \"IT'S TOO BIG!! IT HURTS!...\"</p>", 3: "<p><strong>Bella:</strong><br>*Crying out* \"STOP IT! MY LORD! YOU'RE TEARING MY INSIDES!\"</p>" }, climaxDialogues: { 0: "<p><strong>Bella:</strong><br>*Crying out* \"PLEASE... MY LORD! IT'S NOT NATURAL!\"</p>", 30: "<p><strong>Bella:</strong><br>*Crying out* \"I- I CAN'T... IT HURTS TOO MUCH!\"</p>", 60: "<p><strong>Bella:</strong><br>*Sobbing* \"LORD PLEASE... STOP!\"</p>", 90: "<p><strong>Bella:</strong><br>*Crying out* \"I THINK I'M ABOUT TO WET MYSELF!... PLEASE JUST END IT!\"</p>" }, cumDialogue: "<p><strong>Bella:</strong><br>*Trembling* \"Please leave me alone...\"</p>", postCumDialogue: "<p><strong>Bella:</strong><br>*Sobbing* \"Please.. Just Leave...\"</p>", onComplete: "gss_000_1" }, { id: "mc-bella-bj", title: "Vivid Dream", position: "bj", controls: [ { id: "faster", label: "Faster", type: "advance", path: "bj" }, { id: "anal", label: "Anal", type: "advance", path: "anal" }, { id: "vaginal", label: "Vaginal", type: "advance", path: "vaginal" }, { id: "cum", label: "Cum", type: "cum" } ], progression: { bj: [1, 3, 4], anal: [1, 2, 3], vaginal: [1, 2, 3] }, videoBasePath: "Bin/Contents/Scenes/Human/Prologue/Sex/InterBJ/", initialDialogue: "<p><strong>Bella:</strong><br>*Looking up* \"Forgive me, my Lord.. I'm quite new at this.. \"</p>", speedDialogues: { 1: null, 3: null, 4: null }, climaxDialogues: { 0: "<p><strong>Bella:</strong><br>*Moaning* \"My Lord... It's so... big... I can barely...\"</p>", 30: "<p><strong>Bella:</strong><br>*Moaning* \"Mmm... Does this feel good my Lord?...\"</p>", 60: "<p><strong>Bella:</strong><br>*GLUG GLUG GLUG* \"......\"</p>", 90: "<p><strong>Bella:</strong><br>*Eyes watering* \"Are you... going to...?\"</p>" }, cumDialogue: "<p><strong>Bella:</strong><br>*Wiping her mouth* \"That was something.. Did I do well My Lord?\"</p>", postCumDialogue: "<p><strong>Bella:</strong><br>*Gasping* \"Mmm! My Lord!.. That.. Was....\"</p>", onComplete: "gss_000_1" }, { id: "mc-sabine-hj-clothed", title: "Throne Room", position: "hj_clothed", controls: [ { id: "faster", label: "Faster", type: "advance", path: "hj_clothed" }, { id: "cum", label: "Cum", type: "cum" } ], progression: { hj_clothed: [2, 3] }, videoBasePath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/HJ/", videoPrefix: "HJ-C-", initialDialogue: "<p><strong>Sabine:</strong><br>*Kneeling nervously* \"I hope this pleases you, my Lord... I'll do my best.\"</p>", speedDialogues: { 1: "<p><strong>Sabine:</strong><br>*Focused* \"Like this, my Lord?... Tell me if I should adjust...\"</p>", 2: "<p><strong>Sabine:</strong><br>*Breathing heavier* \"You seem to be enjoying this... I'm glad...\"</p>" }, climaxDialogues: { 0: "<p><strong>Sabine:</strong><br>*Tentatively* \"Is this the right pace, my Lord?\"</p>", 30: "<p><strong>Sabine:</strong><br>*Growing confident* \"Your pleasure is my reward...\"</p>", 60: "<p><strong>Sabine:</strong><br>*Blushing* \"I can feel it throbbing... Are you close, my Lord?\"</p>", 90: "<p><strong>Sabine:</strong><br>*Eagerly* \"Please, my Lord... let me finish you...\"</p>" }, cumDialogue: "<p><strong>Sabine:</strong><br>*Surprised but pleased* \"Oh! My Lord...! I did it...\"</p>", postCumDialogue: "<p><strong>Sabine:</strong><br>*Wiping her hands* \"I hope that was satisfactory, my Lord. I am at your service.\"</p>", onComplete: "returnToCompanionInventory" }, { id: "mc-sabine-hj-naked", title: "Throne Room", position: "hj_naked", controls: [ { id: "faster", label: "Faster", type: "advance", path: "hj_naked" }, { id: "cum", label: "Cum", type: "cum" } ], progression: { hj_naked: [2, 3] }, videoBasePath: "Bin/Contents/Characters/Human_Storyline/Story/SP6/Scenes/HJ/", videoPrefix: "HJ-N-", initialDialogue: "<p><strong>Sabine:</strong><br>*Exposed and blushing* \"Being bare before you like this... it's embarrassing, but... I'll serve you well, my Lord.\"</p>", speedDialogues: { 1: "<p><strong>Sabine:</strong><br>*Shy but determined* \"Can you see everything, my Lord?... Does my body please you?\"</p>", 2: "<p><strong>Sabine:</strong><br>*More aroused* \"Mmm... Touching you like this... I'm starting to feel strange too...\"</p>" }, climaxDialogues: { 0: "<p><strong>Sabine:</strong><br>*Nervously* \"I feel so exposed... but I want to please you...\"</p>", 30: "<p><strong>Sabine:</strong><br>*Warming up* \"Your gaze on my body... it makes me feel... desired...\"</p>", 60: "<p><strong>Sabine:</strong><br>*Breathing heavily* \"I can tell you're enjoying the view, my Lord... and my hands...\"</p>", 90: "<p><strong>Sabine:</strong><br>*Eagerly* \"Please, my Lord... release yourself... I want to see it...\"</p>" }, cumDialogue: "<p><strong>Sabine:</strong><br>*Watching intently* \"Oh my...! So much, my Lord...!\"</p>", postCumDialogue: "<p><strong>Sabine:</strong><br>*Satisfied smile* \"That was... quite something. Thank you for letting me serve you this way.\"</p>", onComplete: "returnToCompanionInventory" } ]>>
:: InteractiveVideoTemplate <div class="fullscreenbackground"> <div class="gui_gametab_manual"> <<include mgui>> <div class="middle_container"> <div class="mc-middle-contents"> <p>Manual Controls Activated: Control with buttons on the right</p> </div> </div> </div> <div class="manual-controls-container"> <div class="manual-controls-left-panel"> <div class="mc-left-panel-content"> <div class="mc-left-panel-title"><p id="mc-title">Vivid Dream</p></div> <div class="mc-left-panel-dialogue" id="mc-dialogue"> <p><strong>Bella:</strong><br></p> </div> </div> </div> <div class="manual-controls-middle-panel"> <div class="mc-video-container"> <video id="mc-scene-video" autoplay> </video> </div> </div> <div class="manual-controls-right-panel"> <div class="mc-top-container"> <div class="mc-controls-title"><p>Climax Bar</p></div> <div class="mc-climax-bar"> <div class="mc-climax-text">Not Ready</div> </div> </div> <div class="mc-bottom-container"> <div class="mc-controls-title"><p>Controls</p></div> <div class="mc-controls-content"> <button class="Faster">Oral</button> <button class="Vaginal">Vaginal</button> <button class="Anal">Anal</button> <button class="Cum">Cum</button> </div> </div> </div> </div> </div> <<script>> $(document).one(':passagedisplay', function () { if (window.InteractiveVideoRenderer) { console.log("Loading interactive video event"); window.InteractiveVideoRenderer.loadInteractiveVideo(); if (window.disableGameButtons) { window.disableGameButtons(); } } else { console.error("InteractiveVideoRenderer not found"); } }); <</script>>
<div class="fullscreenbackground"> <center> <div class="character_creation"> <div class="left_menu_navigation_panel"> <div class="tab-buttons"> <button class="tab-button-1" data-tab="character" data-tooltip-placement="top" onclick="goToCCPassage('CharacterCreation')"></button> <button class="tab-button-2" data-tab="attributes" data-tooltip-placement="top" onclick="goToCCPassage('GodGamer')"></button> <button class="tab-button-3" data-tab="traits" data-tooltip-placement="top" onclick="goToCCPassage('SuperGamer')"></button> <button class="tab-button-4" data-tab="backstory" data-tooltip-placement="top" onclick="goToCCPassage('FuckingEasy')"></button> <button class="tab-button-5" data-tab="game_settings" data-tooltip-placement="top" onclick="goToCCPassage('KDAPlayer')"></button> </div> <div class="ca-info-title"><p>Origins</p></div> <div id="race_selection"> <button class="human_icon" onclick="window.selectRace('Human')"><p>Human</p></button> <div class="elf_icon"><p>Elf</p></div> <div class="dwarf_icon"><p>Dwarf</p></div> <div class="orc_icon"><p>Orc</p></div> </div> <div class="name-inputs-container"> <div class="name-inputs"> <<textboxplus "$firstName" "" "First Name">><</textboxplus>> </div> </div> <div class="name-inputs-container"> <div class="name-inputs"> <<textboxplus "$lastName" "" "Last Name">><</textboxplus>> </div> </div> <div class="name-inputs-container"> <div class="name-inputs"> <<textboxplus "$settlementName" "" "Settlement Name">><</textboxplus>> </div> </div> <div class="random-button"> <button onclick="generateRandomName()" type="button"><p>Random</p></button> </div> </div> <div class="ca-character-preview"> <video autoplay loop muted> <source src="Bin/Contents/Scenes/Human/Character_Creation/Idle.webm" type="video/webm"> </video> </div> <div class="summary_panel"> <div class="summary_container"> <span class="summary-title">Summary</span> <div class="points-section"> <p>Points Remaining: <span id="points-remaining">35</span></p> </div> <div class="attributes-section"> <p>Attributes:</p> <div id="attributes-summary"> <!-- Attributes will be populated here --> </div> </div> <div class="traits-section"> <p>Traits:</p> <div id="traits-summary"> <!-- Selected traits will be populated here --> </div> </div> </div> <div class="swap_button" onclick="toggleSummaryView()"> </div> <div class="agree_button_4" onclick="validateAndProceed()"> <p>Proceed</p> </div> </div> </div> </center> </div>
<<set setup.companionActions = { sabine_6: { interact: { header: "Interact", tags: [], greeting: { auto: true, overrides: [ { id: "corrupted", stat: "Willpower", min: -50, max: -25, text: "My Lord... I await your commands.", voice: "SP6_Inter00_R6_01" }, { id: "mind_breaking", stat: "Willpower", min: -75, max: -51, text: "Please, use me as you see fit, My Lord.", voice: "SP6_Inter00_R7_01" }, { id: "mind_broken", stat: "Willpower", min: -100, max: -76, text: "My Lord. This whore is awaiting your command.", voice: "SP6_Inter00_R8_01" } ], tiers: [ { id: "angry", stat: "Affection", min: -100, max: -25, text: "Yes? What do you want?", voice: "SP6_Inter00_R1_01" }, { id: "neutral", stat: "Affection", min: -25, max: 25, text: "My Lord?", voice: "SP6_Inter00_R2_01" }, { id: "friends", stat: "Affection", min: 26, max: 50, text: "Greetings My Lord!", voice: "SP6_Inter00_R3_01" }, { id: "lovers", stat: "Affection", min: 51, max: 75, text: "Greetings, My Lord! Have you come to see me?", voice: "SP6_Inter00_R4_01" }, { id: "soulmates", stat: "Affection", min: 76, max: 100, requiresMarriage: true, text: "Greetings, Husband. I've missed you", voice: "SP6_Inter00_R5_01" } ], defaultText: "My Lord?" }, actions: [ { id: "menu_talk", label: "Talk", type: "menu", autoRunChildId: "inter01_wellbeing", children: [ { id: "inter01_wellbeing", label: "Ask how she feel.", type: "local", responses: { overrides: [ { id: "corrupted", stat: "Willpower", min: -50, max: -25, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "I feel... empty, but in a good way. Your commands are all I need.", voice: "SP6_Inter01_R6_01" } ] }, { id: "mind_breaking", stat: "Willpower", min: -75, max: -51, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "My body craves your dominance, My Lord. Please use and abuse me.", voice: "SP6_Inter01_R7_01" } ] }, { id: "mind_broken", stat: "Willpower", min: -100, max: -76, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "My Lord... This whore exists only to be used by you. Please tear my holes apart.", voice: "SP6_Inter01_R8_01" } ] } ], tiers: [ { id: "angry", stat: "Affection", min: -100, max: -25, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "I'm barely holding it together, thanks to you. Just leave me be!", voice: "SP6_Inter01_R1_01" } ] }, { id: "neutral", stat: "Affection", min: -25, max: 25, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "Well it's just another day. Is there something you need?", voice: "SP6_Inter01_R2_01" } ] }, { id: "friends", stat: "Affection", min: 26, max: 50, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "Feeling better than I have in a while. Your support means a lot.", voice: "SP6_Inter01_R3_01" } ] }, { id: "lovers", stat: "Affection", min: 51, max: 75, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "Every moment without you feels like an eternity.", voice: "SP6_Inter01_R4_01" } ] }, { id: "soulmates", stat: "Affection", min: 76, max: 100, requiresMarriage: true, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "Being with you is my paradise. You're my everything.", voice: "SP6_Inter01_R5_01" } ] } ], defaultText: "Well it's just another day. Is there something you need?" } }, { id: "inter02_history", label: "Ask about her backstory", type: "local", onceKey: "sabine_inter02_history_once", onceKeyTiers: ["friends_low"], cooldownMessage: "Sabine has already shared her past with you.", responses: { tiers: [ { id: "angry", stat: "Affection", min: -100, max: -25, text: "Why do you care? It's none of your business.", voice: "SP6_Inter02_R1_01" }, { id: "neutral", stat: "Affection", min: -25, max: 25, sequence: [ { speaker: "player", text: "Tell me about your past." }, { speaker: "npc", text: "It's not really something i like to talk about right now. Maybe next time?", voice: "SP6_Inter02_R2_01" } ] }, { id: "friends_low", stat: "Affection", min: 26, max: 100, sequence: [ { speaker: "player", text: "Tell me about your past." }, { speaker: "npc", text: "I don't speak of it often. Not because it is secret, but because talking about it hurts.", voice: "SP6_Inter02_R3_01" }, { speaker: "player", text: "It's okay, I won't judge." }, { speaker: "npc", text: "I'm sorry, but I can't continue talking about it right now. It's just too much. Maybe another time?", voice: "SP6_Inter02_R3_00" } ] } ], defaultText: "It's not really something I like to talk about right now. Maybe next time?" } }, { id: "inter03_aspirations", label: "What are your hopes and dreams?", type: "local", responses: { overrides: [ { id: "corrupted", stat: "Willpower", min: -50, max: -25, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "These days... I only look forward to your commands, My Lord.", voice: "SP6_Inter03_R6_01" } ] }, { id: "mind_breaking", stat: "Willpower", min: -75, max: -51, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "Your desires are my desires, my Lord. I exist only to please you.", voice: "SP6_Inter03_R7_01" } ] }, { id: "mind_broken", stat: "Willpower", min: -100, max: -76, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "This whore's dream is to always be by your cock, and pleasing it every time it wants.", voice: "SP6_Inter03_R8_01" } ] } ], tiers: [ { id: "angry", stat: "Affection", min: -100, max: -25, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "Why should I tell you? It's none of your business.", voice: "SP6_Inter03_R1_01" } ] }, { id: "neutral", stat: "Affection", min: -25, max: 25, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "I suppose I'd like to find a place where I truly belong.", voice: "SP6_Inter03_R2_01" } ] }, { id: "friends", stat: "Affection", min: 26, max: 50, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "My dream is to find love and build a life with someone who understands me.", voice: "SP6_Inter03_R3_01" } ] }, { id: "lovers", stat: "Affection", min: 51, max: 75, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "I dream of building a family with you, a palace filled with love and laughter.", voice: "SP6_Inter03_R4_01" } ] }, { id: "soulmates", stat: "Affection", min: 76, max: 100, requiresMarriage: true, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "I dream of a lifetime of adventures and love, just you and me.", voice: "SP6_Inter03_R5_01" } ] } ], defaultText: "I suppose I'd like to find a place where I truly belong." } }, { id: "inter04_about_me", label: "Ask what does she think of you.", type: "local", responses: { overrides: [ { id: "corrupted", stat: "Willpower", min: -50, max: -25, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "You've twisted me, and I love it. You're the only one who truly understands me.", voice: "SP6_Inter04_R6_01" } ] }, { id: "mind_breaking", stat: "Willpower", min: -75, max: -51, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "Your touch, your commands... they're all I think about, my Lord. You're my everything.", voice: "SP6_Inter04_R7_01" } ] }, { id: "mind_broken", stat: "Willpower", min: -100, max: -76, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "Your words are laws to this whore, My Lord. You are my God.", voice: "SP6_Inter04_R8_01" } ] } ], tiers: [ { id: "angry", stat: "Affection", min: -100, max: -25, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "You? I wish I'd never met you.", voice: "SP6_Inter04_R1_01" } ] }, { id: "neutral", stat: "Affection", min: -25, max: 25, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "As our Lord, you have my loyalty. I will fight for your honor.", voice: "SP6_Inter04_R2_01" } ] }, { id: "friends", stat: "Affection", min: 26, max: 50, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "You're not just our Lord, you're a good one. I respect and admire you.", voice: "SP6_Inter04_R3_01" } ] }, { id: "lovers", stat: "Affection", min: 51, max: 75, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "Every time I think of you, my heart skips a beat. You're incredible.", voice: "SP6_Inter04_R4_01" } ] }, { id: "soulmates", stat: "Affection", min: 76, max: 100, requiresMarriage: true, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "You're my everything. I can't imagine my life without you.", voice: "SP6_Inter04_R5_01" } ] } ], defaultText: "As our Lord, you have my loyalty. I will fight for your honor." } } ] }, { id: "menu_actions", label: "Actions", type: "menu", children: [ ] }, { id: "menu_friendly", label: "Friendly Interactions", type: "menu", children: [ ] }, { id: "menu_romantic", label: "Romantic Interaction", type: "menu", children: [ { id: "inter14_confess_love", label: "Confess Love", type: "local", responses: { overrides: [ { id: "corrupted", stat: "Willpower", min: -50, max: -25, sequence: [ { speaker: "player", text: "I love you, Sabine." }, { speaker: "npc", text: "My loyalty is yours, body and soul. Show me what love feels like under your command.", voice: "SP6_Inter14_R6_01" } ] }, { id: "mind_breaking", stat: "Willpower", min: -75, max: -51, sequence: [ { speaker: "player", text: "I love you, Sabine." }, { speaker: "npc", text: "Please, use me for your pleasure. I will embody it. Break me and make my holes your favorite toys.", voice: "SP6_Inter14_R7_01" } ] }, { id: "mind_broken", stat: "Willpower", min: -100, max: -76, sequence: [ { speaker: "player", text: "I love you, Sabine." }, { speaker: "npc", text: "Love... pleasure... pain... all as you command. This whore is nothing without your rule.", voice: "SP6_Inter14_R8_01" } ] } ], tiers: [ { id: "angry", stat: "Affection", min: -100, max: -25, sequence: [ { speaker: "player", text: "I love you, Sabine." }, { speaker: "npc", text: "What? Are you crazy? No! Get lost!", voice: "SP6_Inter14_R1_01" } ] }, { id: "neutral", stat: "Affection", min: -25, max: 49, sequence: [ { speaker: "player", text: "I love you, Sabine." }, { speaker: "npc", text: "You're very kind to say that, My Lord. But I think we're better off as friends for now, and get to know each other more.", voice: "SP6_Inter14_R2_01" } ] }, { id: "friends", stat: "Affection", min: 50, max: 100, sequence: [ { speaker: "player", text: "I love you, Sabine." }, { speaker: "npc", text: "My Lord? Really? You... you love me? That's... that's wonderful! I've been feeling the same way. Yes, let's see where this goes.", voice: "SP6_Inter14_R3_01" } ] } ], defaultText: "You're very kind to say that, My Lord. But I think we're better off as friends for now." } }, { id: "inter07_marriage", label: "Propose Marriage", type: "local", unlock: { overrides: [ { id: "mind_broken", stat: "Willpower", min: -100, max: -76, text: "" } ], tiers: [ { id: "soulmates", stat: "Affection", min: 76, max: 100, text: "" } ] }, responses: { overrides: [ { id: "mind_broken", stat: "Willpower", min: -100, max: -76, sequence: [ { speaker: "player", text: "Sabine, will you marry me?" }, { speaker: "npc", text: "Yes, My Lord. This whore will forever be your obedient wife, to serve and obey you forever." } ] } ], tiers: [ { id: "soulmates", stat: "Affection", min: 76, max: 100, sequence: [ { speaker: "player", text: "Sabine, will you marry me?" }, { speaker: "npc", text: "Yes! A thousand times, yes! I've dreamed of this moment forever. Of course I'll marry you!" } ] } ], defaultText: "I need more time before I can answer that." } }, { id: "inter10_undress", label: "Undress", type: "local", responses: { overrides: [ { id: "corrupted", stat: "Willpower", min: -50, max: -25, allowsUndress: true, sequence: [ { speaker: "npc", text: "Yes My Lord, as you command.", voice: "SP6_Inter10_R6_01" } ] }, { id: "mind_breaking", stat: "Willpower", min: -75, max: -51, allowsUndress: true, sequence: [ { speaker: "npc", text: "Yes My Lord, as you wish.", voice: "SP6_Inter10_R7_01" } ] }, { id: "mind_broken", stat: "Willpower", min: -100, max: -76, allowsUndress: true, sequence: [ { speaker: "npc", text: "Yes My Lord. This whore forgets her place.", voice: "SP6_Inter10_R8_01" } ] } ], tiers: [ { id: "angry", stat: "Affection", min: -100, max: 0, allowsUndress: false, sequence: [ { speaker: "npc", text: "Fuck off! I'd rather die than undress for someone like you.", voice: "SP6_Inter10_R1_01" } ] }, { id: "neutral", stat: "Affection", min: 1, max: 19, allowsUndress: false, sequence: [ { speaker: "npc", text: "Undress? That's a strange request. No thanks.", voice: "SP6_Inter10_R2_01" } ] }, { id: "friends", stat: "Affection", min: 20, max: 50, allowsUndress: false, sequence: [ { speaker: "npc", text: "Uh, that's a bit personal, don't you think? Let's keep it professional.", voice: "SP6_Inter10_R3_01" } ] }, { id: "lovers", stat: "Affection", min: 51, max: 75, allowsUndress: true, sequence: [ { speaker: "npc", text: "Oh? You want to see me bare? Well then. Enjoy the show, my love.", voice: "SP6_Inter10_R4_01" } ] }, { id: "soulmates", stat: "Affection", min: 76, max: 100, requiresMarriage: true, allowsUndress: true, sequence: [ { speaker: "npc", text: "Of course, my love. My body, my soul, they all belong to you. My husband.", voice: "SP6_Inter10_R5_01" } ] } ], defaultText: "Undress? That's a strange request. No thanks.", defaultAllowsUndress: false } }, { id: "inter15_request_sex", label: "Let's make love", type: "local", responses: { overrides: [ { id: "corrupted", stat: "Willpower", min: -50, max: -25, sequence: [ { speaker: "npc", text: "Use me however you want. My body is yours to command.", voice: "SP6_Inter15_R6_01" } ] }, { id: "mind_breaking", stat: "Willpower", min: -75, max: -51, sequence: [ { speaker: "player", text: "Let's make love." }, { speaker: "npc", text: "Yes, My Lord... whatever you desire. My holes are for you to break.", voice: "SP6_Inter15_R7_01" } ] }, { id: "mind_broken", stat: "Willpower", min: -100, max: -76, sequence: [ { speaker: "player", text: "Let's make love." }, { speaker: "npc", text: "Thank you for even considering to use this whore, My Lord. Please destroy my useless holes.", voice: "SP6_Inter15_R8_01" } ] } ], tiers: [ { id: "angry", stat: "Affection", min: -100, max: -25, sequence: [ { speaker: "player", text: "Let's make love." }, { speaker: "npc", text: "What? Are you crazy? Fuck off!", voice: "SP6_Inter15_R1_01" } ] }, { id: "neutral", stat: "Affection", min: -25, max: 25, sequence: [ { speaker: "player", text: "Let's make love." }, { speaker: "npc", text: "Umm... Sorry, My Lord. But I'm not that kind of girl.", voice: "SP6_Inter15_R2_01" } ] }, { id: "friends", stat: "Affection", min: 26, max: 50, target: "inter15_r3_01_01", targetType: "story", sequence: [ { speaker: "player", text: "Let's make love." }, { speaker: "npc", text: "Right away, My Lord.", voice: "SP6_Inter15_R3_01" } ] }, { id: "lovers", stat: "Affection", min: 51, max: 75, target: "inter15_r3_01_01a", targetType: "story", text: "Lead the way, my love.", voice: "SP6_Inter15_R4_01", }, { id: "soulmates", stat: "Affection", min: 76, max: 100, requiresMarriage: true, target: "inter15_r3_01_01a", targetType: "story", sequence: [ { speaker: "player", text: "Let's make love." }, { speaker: "npc", text: "I've been thinking about that too. Lead the way, husband.", voice: "SP6_Inter15_R5_01" } ] } ], defaultText: "Umm... Sorry, My Lord. But I'm not that kind of girl." } } ] }, ] } }, bella_1: { interact: { header: "Interact", tags: [], greeting: { auto: true, overrides: [ { id: "corrupted", stat: "Willpower", min: -50, max: -25, sequence: [ { speaker: "npc", text: "My Lord, I'm ready, if you wish to use me.", voice: "Inter00_R6_01" } ] }, { id: "mind_breaking", stat: "Willpower", min: -75, max: -51, sequence: [ { speaker: "npc", text: "My Lord, please... My body has been aching for your touch.", voice: "Inter00_R7_01" } ] }, { id: "mind_broken", stat: "Willpower", min: -100, max: -76, sequence: [ { speaker: "npc", text: "Greetings, My Lord Master! This slave's body is ready to pleasure you.", voice: "Inter00_R8_01" } ] } ], tiers: [ { id: "angry", stat: "Affection", min: -100, max: -25, sequence: [ { speaker: "npc", text: "What do you want?", voice: "Inter00_R1_01" } ] }, { id: "neutral", stat: "Affection", min: -25, max: 25, sequence: [ { speaker: "npc", text: "Yes? My Lord? What do you need?", voice: "Inter00_R2_01" } ] }, { id: "friends", stat: "Affection", min: 26, max: 50, sequence: [ { speaker: "npc", text: "G-Greetings, My Lord.", voice: "Inter00_R3_01" } ] }, { id: "lovers", stat: "Affection", min: 51, max: 75, sequence: [ { speaker: "npc", text: "My Lord, I'm glad you've summoned me.", voice: "Inter00_R4_01" } ] }, { id: "soulmates", stat: "Affection", min: 76, max: 100, requiresMarriage: true, sequence: [ { speaker: "npc", text: "Welcome back, My Lord Husband. I missed you.", voice: "Inter00_R5_01" } ] } ], defaultText: "Yes? My Lord? What do you need?" }, actions: [ { id: "menu_talk", label: "Talk", type: "menu", autoRunChildId: "inter01_wellbeing", children: [ { id: "inter01_wellbeing", label: "Ask how she feel.", type: "local", responses: { overrides: [ { id: "corrupted", stat: "Willpower", min: -50, max: -25, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "I feel calmer when you touch me, My Lord. It helps me think.", voice: "Inter01_R6_01" } ] }, { id: "mind_breaking", stat: "Willpower", min: -75, max: -51, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "I feel... a void in me when you are not inside me, My Lord.", voice: "Inter01_R7_01" } ] }, { id: "mind_broken", stat: "Willpower", min: -100, max: -76, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "My Lord Master. My body has been throbbing with needs for your attention.", voice: "Inter01_R8_01" } ] } ], tiers: [ { id: "angry", stat: "Affection", min: -100, max: -25, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "I don't think you really care. Please just leave me be.", voice: "Inter01_R1_01" } ] }, { id: "neutral", stat: "Affection", min: -25, max: 25, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "I am feeling alright, My Lord. Just doing my duties.", voice: "Inter01_R2_01" } ] }, { id: "friends", stat: "Affection", min: 26, max: 50, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "I feel better than before. You've been very kind to me.", voice: "Inter01_R3_01" } ] }, { id: "lovers", stat: "Affection", min: 51, max: 75, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "When you're not around, I feel lonely. I've missed you a lot.", voice: "Inter01_R4_01" } ] }, { id: "soulmates", stat: "Affection", min: 76, max: 100, requiresMarriage: true, sequence: [ { speaker: "player", text: "How are you feeling?" }, { speaker: "npc", text: "As long as I'm with you, I feel safe. And honestly that is all I really need, My Lord Husband.", voice: "Inter01_R5_01" } ] } ], defaultText: "I am feeling alright, My Lord. Just doing my duties." } }, { id: "inter02_history", label: "Ask about her backstory.", type: "local", onceKey: "bella_inter02_history_once", onceKeyTiers: ["friends"], cooldownMessage: "Bella has already shared her past with you.", responses: { tiers: [ { id: "angry", stat: "Affection", min: -100, max: -25, sequence: [ { speaker: "player", text: "Tell me about your past" }, { speaker: "npc", text: "Why would you ask me that? No, let's not talk about it.", voice: "Inter02_R1_01" } ] }, { id: "neutral", stat: "Affection", min: -25, max: 25, sequence: [ { speaker: "player", text: "Tell me about your past" }, { speaker: "npc", text: "I do not feel like talking about that right now. I am sorry.", voice: "Inter02_R2_01" } ] }, { id: "friends", stat: "Affection", min: 26, max: 100, sequence: [ { speaker: "player", text: "Tell me about your past." }, { speaker: "npc", text: "Um... it's not very interesting. My Lord, there's really nothing much to tell.", voice: "Inter01_R2_01" }, { speaker: "player", text: "It's alright. Take your time." }, { speaker: "npc", text: "Okay... Well... My Lord, I was born in a small village. My parents worked the fields. and That's what they've always done. They taught me to listen, and to be helpful.", voice: "Inter01_R2_02" }, { speaker: "npc", text: "Sorry, My Lord. That's all right now.", voice: "Inter01_R2_00" } ] } ], defaultText: "I do not feel like talking about that right now. I am sorry." } }, { id: "inter03_aspirations", label: "Ask about her hopes and dreams", type: "local", responses: { overrides: [ { id: "corrupted", stat: "Willpower", min: -50, max: -25, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "I do not really dream anymore, My Lord. I just wait for your commands.", voice: "Inter03_R6_01" } ] }, { id: "mind_breaking", stat: "Willpower", min: -75, max: -51, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "To be your plaything, My Lord, and for my body to be abused by you.", voice: "Inter03_R7_01" } ] }, { id: "mind_broken", stat: "Willpower", min: -100, max: -76, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "I have everything I hope and dream of, My Lord Master. Being your perfect servant continues to bring me great pleasure.", voice: "Inter03_R8_01" } ] } ], tiers: [ { id: "angry", stat: "Affection", min: -100, max: -25, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "You're not worthy to hear about it. Now... leave me be.", voice: "Inter03_R1_01" } ] }, { id: "neutral", stat: "Affection", min: -25, max: 25, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "I just hope that I do my duties well, and not disappoint you.", voice: "Inter03_R2_01" } ] }, { id: "friends", stat: "Affection", min: 26, max: 50, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "I hope I can keep being useful to you, and not disappoint you. As you have been a kind master to me.", voice: "Inter03_R3_01" } ] }, { id: "lovers", stat: "Affection", min: 51, max: 75, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "I hope I can continue to stay by your side. That's enough for me.", voice: "Inter03_R4_01" } ] }, { id: "soulmates", stat: "Affection", min: 76, max: 100, requiresMarriage: true, sequence: [ { speaker: "player", text: "What are your hopes and dreams?" }, { speaker: "npc", text: "My dream is to serve you for the rest of my life, My Lord Husband.", voice: "Inter03_R5_01" } ] } ], defaultText: "I just hope that I do my duties well, and not disappoint you." } }, { id: "inter04_about_me", label: "Ask what does she think of you.", type: "local", responses: { overrides: [ { id: "corrupted", stat: "Willpower", min: -50, max: -25, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "You are my Lord. I have to obey you, regardless of what I think.", voice: "Inter04_R6_01" } ] }, { id: "mind_breaking", stat: "Willpower", min: -75, max: -51, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "You are my Lord, and my thoughts do not matter other than what pleases you. Your approval is the only thing that matters.", voice: "Inter04_R7_01" } ] }, { id: "mind_broken", stat: "Willpower", min: -100, max: -76, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "What I think no longer matters. This body exists to reflect your will and to bring you pleasure, My Lord Master.", voice: "Inter04_R8_01" } ] } ], tiers: [ { id: "angry", stat: "Affection", min: -100, max: -25, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "I do not like you right now. Leave me be.", voice: "Inter04_R1_01" } ] }, { id: "neutral", stat: "Affection", min: -25, max: 25, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "You are my Lord, and I think you're doing the best you could.", voice: "Inter04_R2_01" } ] }, { id: "friends", stat: "Affection", min: 26, max: 50, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "You are kinder than most masters, My Lord. I trust you.", voice: "Inter04_R3_01" } ] }, { id: "lovers", stat: "Affection", min: 51, max: 75, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "Whenever I am close to you, my heart feels warm, My Lord.", voice: "Inter04_R4_01" } ] }, { id: "soulmates", stat: "Affection", min: 76, max: 100, requiresMarriage: true, sequence: [ { speaker: "player", text: "What do you think of me?" }, { speaker: "npc", text: "You are my whole world, My Lord Husband. You will always be the best in my heart.", voice: "Inter04_R5_01" } ] } ], defaultText: "You are my Lord, and I think you're doing the best you could." } } ] }, { id: "menu_actions", label: "Actions", type: "menu", autoRunFirstChild: true, children: [ ] }, { id: "menu_romantic", label: "Romantic Interaction", type: "menu", children: [ { id: "inter14_confess_love", label: "Confess Love", type: "local", responses: { overrides: [ { id: "corrupted", stat: "Willpower", min: -50, max: -25, sequence: [ { speaker: "player", text: "Confess Love" }, { speaker: "npc", text: "You are too kind, My Lord. But my love is already yours. So tell me how you want me to love you, My Lord.", voice: "Inter14_R6_01" } ] }, { id: "mind_breaking", stat: "Willpower", min: -75, max: -51, sequence: [ { speaker: "player", text: "Confess Love" }, { speaker: "npc", text: "This servant is undeserving of her Lord's love, My Lord. I will cherish it dearly.", voice: "Inter14_R7_01" } ] }, { id: "mind_broken", stat: "Willpower", min: -100, max: -76, sequence: [ { speaker: "player", text: "Confess Love" }, { speaker: "npc", text: "My Lord Master! This servant thanks you for your love, please show this servant your love. This body is ready.", voice: "Inter14_R8_01" } ] } ], tiers: [ { id: "angry", stat: "Affection", min: -100, max: -25, sequence: [ { speaker: "player", text: "Confess Love" }, { speaker: "npc", text: "Please do not joke about things like that.", voice: "Inter14_R1_01" } ] }, { id: "neutral", stat: "Affection", min: -25, max: 25, sequence: [ { speaker: "player", text: "Confess Love" }, { speaker: "npc", text: "You are too kind to say that, My Lord, but I do not feel that way with you.", voice: "Inter14_R2_01" } ] }, { id: "friends", stat: "Affection", min: 26, max: 100, sequence: [ { speaker: "player", text: "Confess Love" }, { speaker: "npc", text: "You love me? I... I think I've been feeling that too. I don't know what it means yet, but I want to try.", voice: "Inter14_R3_01" } ] } ], defaultText: "You are too kind to say that, My Lord, but I do not feel that way with you." } }, { id: "inter07_marriage", label: "Propose Marriage", type: "local", unlock: { overrides: [ { id: "mind_broken", stat: "Willpower", min: -100, max: -76, text: "" } ], tiers: [ { id: "lovers", stat: "Affection", min: 51, max: 75, text: "" } ] }, responses: { overrides: [ { id: "mind_broken", stat: "Willpower", min: -100, max: -76, sequence: [ { speaker: "player", text: "Propose Marriage" }, { speaker: "npc", text: "Yes, My Lord Master. This servant's body will serve you faithfully and give you many sons.", voice: "Inter07_R8_01" } ] } ], tiers: [ { id: "lovers", stat: "Affection", min: 51, max: 75, sequence: [ { speaker: "player", text: "Propose Marriage" }, { speaker: "npc", text: "Yes! I've never wanted anything like this before. If you'll have me... of course I will be your wife.", voice: "Inter07_R4_01" } ] } ], defaultText: "I need more time before I can answer that." } }, { id: "inter10_undress", label: "Undress", type: "local", responses: { overrides: [ { id: "corrupted", stat: "Willpower", min: -50, max: -25, allowsUndress: true, sequence: [ { speaker: "npc", text: "Yes, My Lord.", voice: "Inter10_R6_01" } ] }, { id: "mind_breaking", stat: "Willpower", min: -75, max: -51, allowsUndress: true, sequence: [ { speaker: "npc", text: "As you command, My Lord.", voice: "Inter10_R7_01" } ] }, { id: "mind_broken", stat: "Willpower", min: -100, max: -76, allowsUndress: true, sequence: [ { speaker: "npc", text: "Right away, My Lord Master.", voice: "Inter10_R8_01" } ] } ], tiers: [ { id: "angry", stat: "Affection", min: -100, max: -25, allowsUndress: false, sequence: [ { speaker: "npc", text: "No! How could you ask me that! Leave now!", voice: "Inter10_R1_01" } ] }, { id: "neutral", stat: "Affection", min: -25, max: 0, allowsUndress: true, sequence: [ { speaker: "npc", text: "Undress? I do not think that's proper. But if you insist.", voice: "Inter10_R2_01" } ] }, { id: "friends", stat: "Affection", min: 1, max: 19, allowsUndress: true, sequence: [ { speaker: "npc", text: "Undress? Alright, but it is embarrassing, My Lord. Please be quick.", voice: "Inter10_R3_01" } ] }, { id: "lovers", stat: "Affection", min: 20, max: 75, allowsUndress: true, sequence: [ { speaker: "npc", text: "Right away! My Lord. Anything for you.", voice: "Inter10_R4_01" } ] }, { id: "soulmates", stat: "Affection", min: 76, max: 100, requiresMarriage: true, allowsUndress: true, sequence: [ { speaker: "npc", text: "Of course, My Lord Husband. I belong to you.", voice: "Inter10_R5_01" } ] } ], defaultText: "Undress? I do not think that's proper. But if you insist.", defaultAllowsUndress: true } }, { id: "inter15_request_sex", label: "Let's make love (Have Sex)", type: "local", responses: { overrides: [ { id: "corrupted", stat: "Willpower", min: -50, max: -25, sequence: [ { speaker: "player", text: "Let's make love" }, { speaker: "npc", text: "Yes, My Lord. Use me as you wish.", voice: "Inter15_R6_01" } ] }, { id: "mind_breaking", stat: "Willpower", min: -75, max: -51, sequence: [ { speaker: "player", text: "Let's make love" }, { speaker: "npc", text: "Right away, My Lord. This servant is thankful for being chosen.", voice: "Inter15_R7_01" } ] }, { id: "mind_broken", stat: "Willpower", min: -100, max: -76, sequence: [ { speaker: "player", text: "Let's make love" }, { speaker: "npc", text: "At once! My Lord Master! This servant's holes are prepared for your arrival.", voice: "Inter15_R8_01" } ] } ], tiers: [ { id: "angry", stat: "Affection", min: -100, max: 0, sequence: [ { speaker: "player", text: "Let's make love" }, { speaker: "npc", text: "What? No! Get away from me.", voice: "Inter15_R1_01" } ] }, { id: "neutral", stat: "Affection", min: 0, max: 19, sequence: [ { speaker: "player", text: "Let's make love" }, { speaker: "npc", text: "I'm sorry, My Lord, I cannot do that.", voice: "Inter15_R2_01" } ] }, { id: "friends", stat: "Affection", min: 20, max: 50, target: "inter15_r3_01_01", targetType: "story", sequence: [ { speaker: "player", text: "Let's make love" }, { speaker: "npc", text: "I am here, My Lord. How may I be of service?", voice: "Inter15_R3_01" } ] }, { id: "lovers", stat: "Affection", min: 51, max: 75, target: "inter15_r3_01_01", targetType: "story", sequence: [ { speaker: "player", text: "Let's make love" }, { speaker: "npc", text: "Not so loud, My Lord. Someone might hear us! Go to your chambers first. I'll meet you there soon.", voice: "Inter15_R4_01" } ] }, { id: "soulmates", stat: "Affection", min: 76, max: 100, requiresMarriage: true, target: "inter15_r3_01_01", targetType: "story", sequence: [ { speaker: "player", text: "Let's make love" }, { speaker: "npc", text: "Lead the way, My Lord Husband.", voice: "Inter15_R5_01" } ] } ], defaultText: "I'm sorry, My Lord, I cannot do that." } } ] } ] } } }>>
:: CompanionStatDefinitions [setup] <<set setup.statDefinitions = { strength: { title: "Strength", description: "Raw physical power. Increases damage with heavy weapons and boosts carrying capacity." }, dexterity: { title: "Dexterity", description: "Agility and precision. Improves accuracy, evasion, and finesse-based actions." }, intelligence: { title: "Intelligence", description: "Reasoning and knowledge. Enhances learning, problem solving, and magic/skill effectiveness." }, willpower: { title: "Willpower", description: "Mental endurance. Improves resistance to fear, stress, and coercion." }, discipline: { title: "Discipline", description: "Self-control and focus. Boosts consistency and resilience under pressure." }, kindness: { title: "Kindness", description: "Empathy and compassion. Affects supportive interactions and morale gains." }, happiness: { title: "Happiness", description: "Overall contentment. Influences motivation, cooperation, and performance." }, beauty: { title: "Beauty", description: "Physical appeal. Impacts social influence and certain dialogue outcomes." }, charisma: { title: "Charisma", description: "Presence and persuasion. Improves negotiation and leadership impact." }, health: { title: "Health", description: "Vitality and endurance. Represents physical condition and survivability." }, stamina: { title: "Stamina", description: "Energy reserves. Determines how long strenuous actions can be sustained." }, affection: { title: "Affection", description: "Bond and trust. Gates deeper interactions and improves cooperation." } }>>
<div class="fullscreenbackground"> <div class="followers-screen"> <div class="followers-book"> <div class="followers-panel followers-panel-left"> <div class="followers-panel-header"><span>Party Members</span></div> <div class="followers-slots"><<nobr>><div class="follower-slot" data-slot-index="0"> <div class="follower-card"> <div class="follower-card-header"> <div class="follower-name"><p>Empty</p></div> </div> <div class="follower-card-body"> <div class="follower-portrait"></div> <div class="follower-info"> <div class="follower-hp"> <div class="follower-hp-bar"> <div class="follower-hp-fill"></div> </div> <div class="follower-hp-text"></div> </div> </div> </div> <div class="follower-mini-actions"> <button class="follower-mini-btn follower-primary-btn" type="button" aria-label="Use bandage" style="background-image: url('Bin/Contents/UI/Buttons/btn_Inter_Bandage.png'); background-size: contain; background-position: center; background-repeat: no-repeat;"></button> <button class="follower-mini-btn follower-inventory-btn" type="button" aria-label="Open inventory" style="background-image: url('Bin/Contents/UI/Buttons/btn_Inter_Interact.png'); background-size: contain; background-position: center; background-repeat: no-repeat;"></button> </div> </div> </div> <div class="follower-slot" data-slot-index="1"> <div class="follower-card"> <div class="follower-card-header"> <div class="follower-name"><p>Empty</p></div> </div> <div class="follower-card-body"> <div class="follower-portrait"></div> <div class="follower-info"> <div class="follower-hp"> <div class="follower-hp-bar"> <div class="follower-hp-fill"></div> </div> <div class="follower-hp-text"></div> </div> </div> </div> <div class="follower-mini-actions"> <button class="follower-mini-btn follower-primary-btn" type="button" aria-label="Use bandage" style="background-image: url('Bin/Contents/UI/Buttons/btn_Inter_Bandage.png'); background-size: contain; background-position: center; background-repeat: no-repeat;"></button> <button class="follower-mini-btn follower-inventory-btn" type="button" aria-label="Open inventory" style="background-image: url('Bin/Contents/UI/Buttons/btn_Inter_Interact.png'); background-size: contain; background-position: center; background-repeat: no-repeat;"></button> </div> </div> </div> <div class="follower-slot" data-slot-index="2"> <div class="follower-card"> <div class="follower-card-header"> <div class="follower-name"><p>Empty</p></div> </div> <div class="follower-card-body"> <div class="follower-portrait"></div> <div class="follower-info"> <div class="follower-hp"> <div class="follower-hp-bar"> <div class="follower-hp-fill"></div> </div> <div class="follower-hp-text"></div> </div> </div> </div> <div class="follower-mini-actions"> <button class="follower-mini-btn follower-primary-btn" type="button" aria-label="Use bandage" style="background-image: url('Bin/Contents/UI/Buttons/btn_Inter_Bandage.png'); background-size: contain; background-position: center; background-repeat: no-repeat;"></button> <button class="follower-mini-btn follower-inventory-btn" type="button" aria-label="Open inventory" style="background-image: url('Bin/Contents/UI/Buttons/btn_Inter_Interact.png'); background-size: contain; background-position: center; background-repeat: no-repeat;"></button> </div> </div> </div> <div class="follower-slot" data-slot-index="3"> <div class="follower-card"> <div class="follower-card-header"> <div class="follower-name"><p>Empty</p></div> </div> <div class="follower-card-body"> <div class="follower-portrait"></div> <div class="follower-info"> <div class="follower-hp"> <div class="follower-hp-bar"> <div class="follower-hp-fill"></div> </div> <div class="follower-hp-text"></div> </div> </div> </div> <div class="follower-mini-actions"> <button class="follower-mini-btn follower-primary-btn" type="button" aria-label="Use bandage" style="background-image: url('Bin/Contents/UI/Buttons/btn_Inter_Bandage.png'); background-size: contain; background-position: center; background-repeat: no-repeat;"></button> <button class="follower-mini-btn follower-inventory-btn" type="button" aria-label="Open inventory" style="background-image: url('Bin/Contents/UI/Buttons/btn_Inter_Interact.png'); background-size: contain; background-position: center; background-repeat: no-repeat;"></button> </div> </div> </div><</nobr>></div> <button class="followers-exit-btn" type="button" data-followers-action="exit">Exit</button> </div> <div class="followers-panel followers-panel-right"> <div class="followers-panel-header"><span>Assignments</span></div> <div class="followers-empty-work"> <div class="followers-empty-text">WORK IN PROGRESS</div> </div> </div> </div> </div> </div> <script> (function() { const travelButton = document.querySelector('[data-followers-action="travel"]'); if (travelButton) { travelButton.addEventListener('click', function() { if (typeof window.handleFollowersTravel === 'function') { window.handleFollowersTravel(); } else { console.warn('[Followers] No travel handler registered.'); } }); } const exitButton = document.querySelector('[data-followers-action="exit"]'); if (exitButton) { exitButton.addEventListener('click', function() { if (typeof window.handleFollowersExit === 'function') { window.handleFollowersExit(); } else if (typeof window.returnToPrevious === 'function') { window.returnToPrevious(); } }); } if (typeof window.updateFollowerUI === 'function') { window.updateFollowerUI(); } })(); </script>
<<run setup.guiDisabledByPassage = { "StoryEventTemplate": { mode: "all", allow: [".setting_button", ".save_button", "[data-gui-button=\"settings\"]", "[data-gui-button=\"save\"]", "#quest-ui-button"] }, "buildings-template": { mode: "all", allow: [".setting_button", ".save_button", "[data-gui-button=\"settings\"]", "[data-gui-button=\"save\"]", "#quest-ui-button"], questIds: ["quest_find_family", "quest_aftermath", "quest_aid_father"] }, "world-template": { disable: ["prison", "castle-management"] } }>>
<div class="fullscreenbackground"> <div class="gui_gametab_shop"> <div class="right_container"> <<include "gui-button">> </div> <div class="left_container_420px"> <div class="basic_information"> <div class="character_icon"> <img src="Bin\Contents\Characters\Human_Storyline\Story\Player\Frame.png" alt="human_avatar"> </div> <<include "characterstatusgui">> </div> <div class="left_container_buttons"> <button class="left_container_button" data-gui-button="settings" data-tooltip="Settings" data-tooltip-placement="top" onclick="openSettings()"> <img src="Bin/Contents/UI/Buttons/sbtn_settings.png" alt="settings"> </button> <button class="left_container_button" data-gui-button="save" data-tooltip="Save" data-tooltip-placement="top" onclick="saveGame()"> <img src="Bin/Contents/UI/Buttons/sbtn_save.png" alt="save"> </button> <div class="return_home"> <button class="left_container_button" data-gui-button="return-home" data-tooltip="Return Home" data-tooltip-placement="top" onclick="window.openLandsOverview(); event.stopPropagation(); return false;"> <img src="Bin/Contents/UI/Buttons/sbtn_rhome.png" alt="return_home"> </button> </div> <button class="left_container_button" data-gui-button="skip-time" data-tooltip="Skip 4 hours (2/day)" data-tooltip-placement="top" onclick="window.useSkipTime(); return false;"> <img src="Bin/Contents/UI/Buttons/sbtn_skiptime.png" alt="time_skip"> </button> <div class="time_and_money"> <div class="dynamic_time_money"> <span class="time_value">12:00 AM | Monday</span> </div> </div> </div> </div> <div class="middle_container_180px"> <div class="si-hear-me-out"> <div class="si-hear-me-out-text"><p>Actions</p></div> <div class="si-big-brain-time"> <<nobr>> <div class="si-big-brain-time-choices" onclick="goToPassage('Blacksmith_first')">- Return.</div> <div class="si-big-brain-time-choices" onclick="goToPassage('area1')">- Leave.</div> <</nobr>> </div> </div> </div> <<include "shop-interface">> </div> </div> <<script>> setTimeout(function() { if (window.BlacksmithSystem) { window.BlacksmithSystem.playerGold = (State.variables.playerState && State.variables.playerState.resources && typeof State.variables.playerState.resources.gold === 'number') ? State.variables.playerState.resources.gold : 0; window.BlacksmithSystem.reputation = State.variables.blacksmithReputation || 50; window.BlacksmithSystem.initializeShop(State.variables.currentLocation || "all"); } if (window.ShopSystem) { window.ShopSystem.init({ type: 'blacksmith', displayMode: 'grid', pricingMode: 'static', showItemStats: true, showOwnerInfo: false, showDemandBadge: false, title: "Henry's Blacksmith" }); } }, 150); <</script>>
<<run setup.guiDisabledByPassage = { "StoryEventTemplate": { mode: "all", allow: [".setting_button", ".save_button", "#quest-ui-button"] }, "buildings-template": { mode: "all", allow: [".setting_button", ".save_button", "#quest-ui-button"], questIds: ["quest_find_family", "quest_aftermath", "quest_aid_father"] }, "world-template": { disable: ["prison", "castle-management"] } }>>
<div class="fullscreenbackground"> <div class="gui_gametab_cb"> <div class="right_container"> <<include "gui-button">> </div> <div class="left_container"> <div class="basic_information"> <div class="character_icon" onclick="openUIScreen('player')"> <img src="Bin/Contents/Characters/Human_Storyline/Story/Player/Frame.png" alt="human_avatar"> </div> <<include "characterstatusgui">> </div> <div class="left_container_buttons"> <button class="left_container_button" data-gui-button="settings" data-tooltip="Settings" data-tooltip-placement="top" onclick="openSettings()"> <img src="Bin/Contents/UI/Buttons/sbtn_settings.png" alt="settings"> </button> <button class="left_container_button" data-gui-button="save" data-tooltip="Save" data-tooltip-placement="top" onclick="saveGame()"> <img src="Bin/Contents/UI/Buttons/sbtn_save.png" alt="save"> </button> <button class="left_container_button" data-gui-button="skip-time" data-tooltip="Skip 30 minutes" data-tooltip-placement="top" onclick="window.useSkipTime(); return false;"> <img src="Bin/Contents/UI/Buttons/sbtn_skiptime.png" alt="time_skip"> </button> <div class="time_and_money"> <div class="dynamic_time_money"> <span class="time_value">12:00 AM | Monday</span> </div> </div> <div class="return_home"> <button class="left_container_button" data-gui-button="return-home" data-tooltip-id="return-home" data-tooltip-placement="top" onclick="if (window.CompassNavigation && typeof window.CompassNavigation.returnToBlackmoorCastle === 'function') { window.CompassNavigation.returnToBlackmoorCastle(); } event.stopPropagation(); return false;"> <img src="Bin/Contents/UI/Buttons/sbtn_rhome.png" alt="return_home"> </button> </div> </div> </div> <div class="middle_container"> <div class="location_name"> <p>Blackmoor Castle</p> <<settlementStatus>> </div> </div> </div> <div class="city-building-game-area"> <div class="city-building-grid-container" id="grid-container"> <canvas id="webgl-grid" style="position: absolute; z-index: 1;"></canvas> <div class="city-building-grid" id="grid" style="position: absolute; z-index: 2; pointer-events: auto; background: transparent;"> </div> <<include "cb-toolbar">> </div> <!-- Day/Night Overlay RE-ADDED --> <div class="city-building-day-night-overlay" id="day-night-overlay"></div> </div> </div> <!-- Toast Container RE-ADDED --> <div class="city-building-toast-container" id="toast-container"></div> <!-- Tooltip RE-ADDED --> <div class="city-building-tooltip" id="tooltip"></div> <!-- Script to switch time system to city mode when this area loads --> <script> function setAreaTitle() { const titleEl = document.querySelector('.location_name p'); if (!titleEl) return; const baseName = (window.getSettlementBaseName && window.getSettlementBaseName()) || 'Blackmoor'; const dynamicName = (window.getSettlementAreaName && window.getSettlementAreaName('area1')) || (window.getAreaDisplayName && window.getAreaDisplayName('area1')) || `${baseName} Castle`; titleEl.textContent = dynamicName; } // Enable dynamic time in city area window.switchMode("city"); console.log("Switched to city mode - time now advances dynamically"); // Area initialization is handled centrally in real-city-building.js // Function to switch back to story mode when leaving the area window.leaveArea = function() { window.switchMode("story"); console.log("Switched to story mode - time now advances only on actions"); // Navigate to the desired passage Engine.play("world_map"); // Replace with your actual passage name }; // Add exit area button document.addEventListener('DOMContentLoaded', function() { setAreaTitle(); // Add an exit button to leave the area (can be customized) const exitButton = document.createElement('button'); exitButton.textContent = "Leave Castle"; exitButton.className = "exit-area-button"; exitButton.style.position = "absolute"; exitButton.style.bottom = "60px"; exitButton.style.left = "20px"; exitButton.style.zIndex = "1000"; exitButton.onclick = window.leaveArea; document.querySelector('.fullscreenbackground').appendChild(exitButton); }); // Run once in case DOMContentLoaded already fired (Twine passage render) setAreaTitle(); </script>